SFINAE 未能处理中间类型特征
SFINAE failing to work with intermediary type traits
考虑以下测试代码:
// 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;
}
代码涉及:
- 没有
::type
的结构invalid
- 结构
valid
具有 ::type
- 将
::type
定义为 T::type
的结构 traits
- 一个重载的
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>, = (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> &>, = (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&&...)
问题:为什么第一个版本不起作用,有没有办法让它与中间类型特征一起工作?
如果你看了SFINAE的描述,有这样一句话:
Only the failures in the types and expressions in the immediate context of the function type or its template parameter types or its explicit specifier (since C++20) are SFINAE errors.
traits<T, U>::type
是在 function
的 直接上下文 中访问的,而不是 sfinae
的sfinae
。这就是它导致编译器错误的原因。
SFINAE 要求替换失败是实例化的 "in the immediate context"。否则会出现硬错误。
如果没有中间类型 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;
};
从根本上说,这归结为 "immediate context" 在 [temp.deduct]/8, the sfinae rule, which isn't super clearly defined (see cwg 1844 中的含义):
If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [ Note: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. — end note ] Only invalid types and expressions in the immediate context of the function type, its template parameter types, and its explicit-specifier can result in a deduction failure. [ Note: The substitution into types and expressions can result in effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such effects are not in the “immediate context” and can result in the program being ill-formed. — end note ]
在这种情况下,可以说直接上下文只是看到 traits<T,U>::type
是一个存在的东西。它是做什么的。但只有当我们检查并将该类型实例化为默认参数时,我们才必须查看 T::type
是什么。但这与我们实际需要的有点延迟。
您需要的是强制 traits
本身的实例化失败,或者如果 T
没有,则强制 traits
没有名为 type
的成员别名.逃脱的简短版本只是:
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,但使用概念画板,您可以:
template <typename T>
concept Typed = requires {
typename T::type;
};
template <Typed T, typename... Args>
struct traits {
using type = typename T::type;
};
考虑以下测试代码:
// 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;
}
代码涉及:
- 没有
::type
的结构 - 结构
valid
具有::type
- 将
::type
定义为T::type
的结构 - 一个重载的
function
只有当第一个参数的类型定义了traits<T>::type
时才应该工作 - 重载的
sfinae
函数应该能够调用function
,即使第一个参数是invalid
invalid
traits
然而,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>, = (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> &>, = (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&&...)
问题:为什么第一个版本不起作用,有没有办法让它与中间类型特征一起工作?
如果你看了SFINAE的描述,有这样一句话:
Only the failures in the types and expressions in the immediate context of the function type or its template parameter types or its explicit specifier (since C++20) are SFINAE errors.
traits<T, U>::type
是在 function
的 直接上下文 中访问的,而不是 sfinae
的sfinae
。这就是它导致编译器错误的原因。
SFINAE 要求替换失败是实例化的 "in the immediate context"。否则会出现硬错误。
如果没有中间类型 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;
};
从根本上说,这归结为 "immediate context" 在 [temp.deduct]/8, the sfinae rule, which isn't super clearly defined (see cwg 1844 中的含义):
If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [ Note: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. — end note ] Only invalid types and expressions in the immediate context of the function type, its template parameter types, and its explicit-specifier can result in a deduction failure. [ Note: The substitution into types and expressions can result in effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such effects are not in the “immediate context” and can result in the program being ill-formed. — end note ]
在这种情况下,可以说直接上下文只是看到 traits<T,U>::type
是一个存在的东西。它是做什么的。但只有当我们检查并将该类型实例化为默认参数时,我们才必须查看 T::type
是什么。但这与我们实际需要的有点延迟。
您需要的是强制 traits
本身的实例化失败,或者如果 T
没有,则强制 traits
没有名为 type
的成员别名.逃脱的简短版本只是:
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,但使用概念画板,您可以:
template <typename T>
concept Typed = requires {
typename T::type;
};
template <Typed T, typename... Args>
struct traits {
using type = typename T::type;
};