SFINAE 和模板函数实例化:为什么在具有启用 SFINAE 的类型的函数参数中使用时无法推导出模板参数?
SFINAE and template function instantiation: Why a template argument cannot be deduced when used in function arguments with a SFINAE-enabled type?
这几天我在用 SFINAE 做实验,有些事情让我很困惑。为什么在my_function
的实例化中不能推导出my_type_a
?
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);
^
我期望的是,当我像在 main
中那样调用 my_function
时,T
将被推断为函数第一个参数的类型,并且该类型将是在函数的实例化中使用。但似乎 my_common_type_t<T>
是在函数之前实例化的,但即便如此, my_cvalue
的类型无论如何都会变成 my_type_a
,所以我不明白为什么这行不通......
有没有其他方法可以做到这一点?我应该避免两层(或更多层)模板间接寻址吗?
嗯,考虑一下:
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
public,否则 GCC 将输出伪造的错误。
这几天我在用 SFINAE 做实验,有些事情让我很困惑。为什么在my_function
的实例化中不能推导出my_type_a
?
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);
^
我期望的是,当我像在 main
中那样调用 my_function
时,T
将被推断为函数第一个参数的类型,并且该类型将是在函数的实例化中使用。但似乎 my_common_type_t<T>
是在函数之前实例化的,但即便如此, my_cvalue
的类型无论如何都会变成 my_type_a
,所以我不明白为什么这行不通......
有没有其他方法可以做到这一点?我应该避免两层(或更多层)模板间接寻址吗?
嗯,考虑一下:
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
public,否则 GCC 将输出伪造的错误。