SFINAE和模板函数实例化:为什么在具有SFINAE启用类型的函数参数中使用模板参数时无法推导出?

时间:2017-07-31 02:27:44

标签: c++ templates c++14 template-deduction

这些天我正在尝试SFINAE,有些事让我感到困惑。为什么my_type_a无法在my_function的实例化中推断出来?

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,所以我不明白为什么这不起作用...... < / p>

有不同的方法吗?我应该避免两个(或更多)级别的模板间接?

1 个答案:

答案 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>

如果该语言允许在您的情况下进行扣除,则必须与T中的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推断的内容是什么? int,简单!编译器对这种匹配很满意。此外,由于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将输出虚假错误。