2017-07-31 113 views
0

这些天我在用SFINAE做实验,还有一些让我感到困惑。为什么my_type_a不能在my_function的实例化中推导出来?SFINAE和模板函数实例化:为什么在使用具有SFINAE的类型的函数参数中使用模板参数时不能推导出来?

class my_type_a {}; 

template <typename T> 
class my_common_type { 
public: 
    constexpr static const bool valid = false; 
}; 

template <> 
class my_common_type<my_type_a> { 
public: 
    constexpr static const bool valid = true; 
    using type = my_type_a; 
}; 

template <typename T> using my_common_type_t = typename my_common_type<T>::type; 

template <typename T, typename V> 
void my_function(my_common_type_t<T> my_cvalue, V my_value) {} 

int main(void) { 
    my_function(my_type_a(), 1.0); 
} 

G ++给了我这样的:

/home/flisboac/test-template-template-arg-subst.cpp: In function ‘int main()’: 
/home/flisboac/test-template-template-arg-subst.cpp:21:30: error: no matching function for call to ‘my_function(my_type_a, double)’ 
    my_function(my_type_a(), 1.0); 
          ^
/home/flisboac/test-template-template-arg-subst.cpp:18:6: note: candidate: template<class T, class V> void my_function(my_common_type_t<T>, V) 
void my_function(my_common_type_t<T> my_type, V my_value) {} 
     ^~~~~~~~~~~ 
/home/flisboac/test-template-template-arg-subst.cpp:18:6: note: template argument deduction/substitution failed: 
/home/flisboac/test-template-template-arg-subst.cpp:21:30: note: couldn't deduce template parameter ‘T’ 
    my_function(my_type_a(), 1.0); 
          ^

我所期待的是,在调用my_function当我在main一样,T会被推断为函数的第一个参数的类型,以及该类型将用于函数的实例化。但似乎my_common_type_t<T>是函数之前实例化,但即使如此,中my_cvalue类型将成为my_type_a反正,所以我不明白为什么这是行不通的......

是否有不同的方式来做到这一点?我应该避免两个(或更多)级别的模板间接?

+0

在'my_common_type :: type'中,'T'在[非推导的上下文](http://en.cppreference.com/w/cpp/language/template_argument_deduction#Non-deduced_contexts)中。你希望编译器用每个可能的类型'T'实例化'my_common_type',希望其中的一个'my_common_type :: type'出来与'my_type_a'兼容;或者参与定理证明练习以尝试并通过分析找到这种类型。编译器不会。 –

+0

@Igor我明白规则1(来自您提供的链接)当然符合我的例子。但是,为什么不清楚'T'不一定是'my_type_a'?如果'my_common_type '在实例化'my_function'之前被实例化,那么类型可能是'my_type_a'或者什么也不是(因此函数将通过SFINAE消除)。如果在编译期间或之后实例化,编译器会将'my_common_type '的信息作为候选(因此,'T = my_type_a'),是不是? –

+0

*“为什么不清楚”*这是我说的定理证明练习。这里的“清除”意味着“可以从现有事实中证明”。也许它可以 - 但编译器不需要拥有这种推理引擎。 –

回答

2

那么,考虑一下:

template <> 
struct my_common_type<int> { 
    constexpr static const bool valid = true; 
    using type = my_type_a; 
}; 

template <> 
struct my_common_type<double> { 
    constexpr static const bool valid = true; 
    using type = my_type_a; 
}; 

// ... 

int main(void) { 
    my_function(my_type_a{}, 1.0); 
} 

该编译器选择my_common_type<int>my_common_type<double>

如果语言允许在您的情况下扣除,它将必须匹配将在my_common_type<T>::type为了产生您发送到函数参数的确切类型。显然,这不仅是不可能的,但以上面的例子来说,它可能有多种选择!

幸运的是,有一种方法可以告诉编译器my_common_type<T>将始终屈服于T。技巧的基本原理是这样的:

template<typename T> 
using test_t = T; 

template<typename T> 
void call(test_t<T>) {} 

int main() { 
    call(1); 
} 

什么是T推演?简单!编译器对这种匹配很满意。此外,由于test_t不能专业化,因此test_t<soxething>仅为something

template<typename T> 
using test_t = T; 

template<typename T> 
using test2_t = test_t<T>; 

template<typename T> 
void call(test2_t<T>) {} 

int main() { 
    call(1); // will also work 
} 

我们可以将此到你的情况,但我们需要一些工具:

template<typename T, typename...> 
using first_t = T; 

这是一样的容易

此外,这与别名的多层次的工作压力太大如上所述匹配,但我们也可以发送一些不会被使用的参数。我们将在这个未使用的包中制作sfinae。

现在,改写my_common_type_t仍然是一件容易的比赛,而在未使用的包添加约束:

template <typename T> 
using my_common_type_t = first_t<T, typename my_common_type<T>::type>; 

注意,这也是工作:

template <typename T> 
using my_common_type_t = first_t<T, std::enable_if_t<my_common_type<T>::valid>>; 

现在扣将发生,因为预期!Live (GCC)Live (Clang)

注意,这一招将与C++ 14在这种情况下,只有工作,因为SFINAE(丢弃参数)时,才能保证,因为C++ 14的情况发生。

另请注意,您应该为您的特征使用struct,或者使用public:来公开成员my_common_type<T>::type,否则GCC将输出一个伪造错误。

+0

哇,这当然是一个不错的选择,它回答了我的问题!但是我的情况有点复杂,因为'my_common_type :: type'是根据模板参数选择的。换句话说,这不仅仅是一个启用的问题。另外,我认为编译器会首先查看函数调用站点,并且看到'T'是'my_type_a',而不管'my_common_type'的特殊性。此外,我认为'my_common_type '和'my_common_type '都不会被选中,因为它们都不是'my_common_type '的专用于'T = my_type_a'的选项。 –

+1

我想更新我的问题,但是您的回答非常有用,我想再提一个问题来回答我的具体问题。 –

+0

@FlávioLisbôa谢谢!总是乐于提供帮助。问另一个问题是在这个网站确实做到这一点的正确方法。 –

相关问题