请考虑以下测试代码:
// Preprocessor
#include <iostream>
#include <type_traits>
// Structure with no type alias
template <class T>
struct invalid {
};
// Structure with a type alias
template <class T>
struct valid {
using type = T;
};
// Traits getting the type of the first type
template <class T, class... Args>
struct traits {
using type = typename T::type;
};
// One argument function
template <class T, class = typename traits<T>::type>
void function(T) {
std::cout << "function(T)" << std::endl;
}
// Two arguments function
template <class T, class U, class = typename traits<T, U>::type>
void function(T, U) {
std::cout << "function(T, U)" << std::endl;
}
// When function can be called on all arguments
template <
class... Args,
class = decltype(function(std::declval<Args>()...))
>
void sfinae(Args&&... args) {
function(std::forward<Args>(args)...);
std::cout << "sfinae(Args&&...)" << std::endl;
}
// When function can be called on all arguments except the first one
template <
class T,
class... Args,
class = decltype(function(std::declval<Args>()...))
>
void sfinae(const invalid<T>&, Args&&... args) {
function(std::forward<Args>(args)...);
std::cout << "sfinae(const invalid<T>&, Args&&...)" << std::endl;
}
// Main function
int main(int argc, char* argv[]) {
valid<int> v;
invalid<int> i;
sfinae(v);
sfinae(i, v);
return 0;
}
该代码涉及:
invalid
的结构::type
valid
有一个::type
traits
,将::type
定义为T::type
function
,只有当第一个参数的类型定义为traits<T>::type
时才应该起作用sfinae
函数应该能够调用function
,即使第一个参数是invalid
但是,SFINAE机制在这种情况下似乎不起作用,我不确定理解为什么。错误如下:
sfinae_problem_make.cpp:19:30: error: no type named 'type' in 'invalid<int>'
using type = typename T::type;
~~~~~~~~~~~~^~~~
sfinae_problem_make.cpp:29:46: note: in instantiation of template class 'traits<invalid<int>, valid<int> >' requested here
template <class T, class U, class = typename traits<T, U>::type>
^
sfinae_problem_make.cpp:30:6: note: in instantiation of default argument for 'function<invalid<int>, valid<int> >' required here
void function(T, U) {
^~~~~~~~~~~~~~~~
sfinae_problem_make.cpp:37:22: note: while substituting deduced template arguments into function template 'function' [with T = invalid<int>, U = valid<int>, $2 = (no value)]
class = decltype(function(std::declval<Args>()...))
^
sfinae_problem_make.cpp:39:6: note: in instantiation of default argument for 'sfinae<invalid<int> &, valid<int> &>' required here
void sfinae(Args&&... args) {
^~~~~~~~~~~~~~~~~~~~~~~~
sfinae_problem_make.cpp:60:5: note: while substituting deduced template arguments into function template 'sfinae' [with Args = <invalid<int> &, valid<int> &>, $1 = (no value)]
sfinae(i, v);
非常令人惊讶的是,如果从问题中删除了特征:
// Preprocessor
#include <iostream>
#include <type_traits>
// Structure with no type alias
template <class T>
struct invalid {
};
// Structure with a type alias
template <class T>
struct valid {
using type = T;
};
// Traits getting the type of the first type
template <class T, class... Args>
struct traits {
using type = typename T::type;
};
// One argument function
template <class T, class = typename T::type>
void function(T) {
std::cout << "function(T)" << std::endl;
}
// Two arguments function
template <class T, class U, class = typename T::type>
void function(T, U) {
std::cout << "function(T, U)" << std::endl;
}
// When function can be called on all arguments
template <
class... Args,
class = decltype(function(std::declval<Args>()...))
>
void sfinae(Args&&... args) {
function(std::forward<Args>(args)...);
std::cout << "sfinae(Args&&...)" << std::endl;
}
// When function can be called on all arguments except the first one
template <
class T,
class... Args,
class = decltype(function(std::declval<Args>()...))
>
void sfinae(const invalid<T>&, Args&&... args) {
function(std::forward<Args>(args)...);
std::cout << "sfinae(const invalid<T>&, Args&&...)" << std::endl;
}
// Main function
int main(int argc, char* argv[]) {
valid<int> v;
invalid<int> i;
sfinae(v);
sfinae(i, v);
return 0;
}
然后它按预期工作并输出:
function(T)
sfinae(Args&&...)
function(T)
sfinae(const invalid<T>&, Args&&...)
问题:为什么第一个版本不起作用,是否有办法使其适用于中间类型特征?
从根本上说,这归结为[temp.deduct]/8中的“直接背景”意味着什么,sfinae规则,并没有超级明确定义(见cwg 1844):
如果替换导致无效的类型或表达式,则类型推导失败。如果使用替换参数写入,则无效的类型或表达式将是格式错误的,需要诊断。 [注意:如果不需要诊断,程序仍然是不正确的。访问检查是替换过程的一部分。 - 结束注释]只有函数类型的直接上下文中的无效类型和表达式,其模板参数类型及其显式说明符才会导致演绎失败。 [注意:替换为类型和表达式可能会产生诸如类模板特化和/或函数模板特化的实例化,隐式定义函数的生成等效果。这些效果不在“直接上下文”中,并且可以导致程序格式不正确。 - 结束说明]
在这种情况下,可以说,直接背景只是看到traits<T,U>::type
是存在的东西。它做的。但只有当我们通过并将该类型实例化为默认参数时,我们才能查看T::type
是什么。但这与我们实际需要的有点延迟。
你需要的是强制traits
本身的实例化失败或迫使traits
没有名为type
的成员别名,如果T
没有。 cop-out短版本只是:
template <class T, class... Args>
struct traits;
template <class T>
struct traits<valid<T>> {
using type = T;
};
但是你会想要比这更强大的东西。
不幸的是,你不能添加一个尾随的默认模板参数,如:
template <typename T, typename... Args, typename = typename T::type>
struct traits {
using type = typename T::type;
};
由于[temp.param]/15,但有了Concepts,你可以这样做:
template <typename T>
concept Typed = requires {
typename T::type;
};
template <Typed T, typename... Args>
struct traits {
using type = typename T::type;
};
SFINAE要求替换失败是在实例化的“直接上下文”中。否则会发生硬错误。
如果没有中间traits
类型,function<invalid<int>, valid<int>, invalid<int>::type>
的实例化会在直接上下文中导致错误,因为invalid<int>
没有名为type
的成员,所以SFINAE开始了。
使用中间traits
类型,错误发生在定义traits<invalid<int>>
的实例化期间,因为这需要不存在的invalid<int>::type
。这不是直接上下文,因此会发生硬错误。
要解决此问题,您必须确保traits
始终具有有效的定义。这可以这样做:
template <class T, class = void>
struct traits {};
template <class T>
struct traits<T, std::void_t<typename T::type>> {
using type = typename T::type;
};
如果您阅读SFINAE的描述,有这样一句话:
只有函数类型的直接上下文中的类型和表达式中的失败或其模板参数类型或其显式说明符(自C ++ 20)才是SFINAE错误。
traits<T, U>::type
可以在function
的直接背景下访问,而不是sfinae
。这就是导致编译器错误的原因。