C++ SFINAE 没有失败
C++ SFINAE not failing
代码:
#include <iostream>
using std::nullptr_t;
template<typename... T>
using nullptr_vt = nullptr_t;
struct not_addable{};
template<
typename T,
nullptr_vt<decltype(std::declval<T>() + std::declval<T>())> TSfinae = nullptr>
bool test_addable(int)
{ return true; }
template<typename>
bool test_addable(...)
{ return false; }
int main()
{
std::cout << std::boolalpha;
std::cout << test_addable<int>(0) << std::endl;
std::cout << test_addable<not_addable>(0) << std::endl;
// Gives error ("invalid operands to binary expression"):
// nullptr_vt<decltype(std::declval<not_addable>() + std::declval<not_addable>())> a{};
}
我认为这会打印:
true
false
,但事实并非如此。它打印:
true
true
。至少 https://repl.it/@Hrle/sfinaetemplatesuccess。
我认为来自第一个重载的 nullptr_vt<decltype(std::declval<T>() + std::declval<T>())>
对 not_addable
来说是一个错误,它会从重载集中丢弃它,从而选择第二个重载。
如果有默认值,编译器是否能够丢弃 TSfinae
的类型?
I thought that nullptr_vt<decltype(std::declval<T>() + std::declval<T>())>
from the first overload would be an error for not_addable and it would discard it from the overload set, thus choosing the second overload.
这个想法其实很好,问题是GCC和nullptr_vt
这一行:
nullptr_vt<decltype(std::declval<T>() + std::declval<T>())> TSfinae = nullptr
在 GCC 10.2 上你不希望它工作的地方工作,但在 Clang 11.0.1 上是正确的。将其更改为
nullptr_vt<decltype(std::declval<T>() + std::declval<T>())> *TSfinae = nullptr
两者都是正确的,更简单的
typename TSfinae = nullptr_vt<decltype(std::declval<T>() + std::declval<T>())>
typename _ = decltype(std::declval<T>() + std::declval<T>())
最后 make_void 把戏
template<typename... T> struct make_nullptr_vt { using type = nullptr_t; };
template<typename T>
using nullptr_vt = typename make_nullptr_vt<T>::type;
也在 GCC 上修复了原始版本。
这并没有说明问题,也没有假装比@Useless 的答案更好,但它是我觉得方便的替代解决方案。
我把typename
换成了一个整数,为了省点写,必要时用逗号运算符来枚举很多条件。
当然,当必须多次使用相同的条件时,带有 using
的别名声明可以帮助提高可读性。
编辑
正如@StoryTeller 评论所建议的,如果我们声明一个 operator,
与最后一个 1
组合,那么 1
将被消耗,我们可以在 decltype()
一种会使 SFINAE 失败的类型。
他建议在 1
之前的条件序列中插入 void()
。
实际上,没有右侧操作数是不可能声明 operator,
的;因此,没有任何东西与此 void()
结合,最终 1
将在 decltype()
中发出。
它不像 1
那样最小,但它更安全。
/**
g++ -std=c++17 -o prog_cpp prog_cpp.cpp \
-pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
-g -O0 -UNDEBUG -fsanitize=address,undefined
**/
#include <iostream>
struct A
{
A operator+(A r);
A operator-(A r);
A operator,(int r); // try to mislead SFINAE
};
struct B
{
B operator+(B r);
// no -
};
struct C
{
// no +
// no -
};
template<
typename T,
decltype((std::declval<T>()+std::declval<T>()),
void(),1) =1>
bool test_add(int)
{ return true; }
template<typename>
bool test_add(...)
{ return false; }
template<
typename T,
decltype((std::declval<T>()+std::declval<T>()),
(std::declval<T>()-std::declval<T>()),
void(),1) =1>
bool test_add_sub(int)
{ return true; }
template<typename>
bool test_add_sub(...)
{ return false; }
template<typename T>
using has_add =
decltype((std::declval<T>()+std::declval<T>()),
void(),1);
template<typename T>
using has_add_sub =
decltype((std::declval<T>()+std::declval<T>()),
(std::declval<T>()-std::declval<T>()),
void(),1);
template<
typename T,
has_add<T> =1>
bool test_add2(int)
{ return true; }
template<typename>
bool test_add2(...)
{ return false; }
template<
typename T,
has_add_sub<T> =1>
bool test_add_sub2(int)
{ return true; }
template<typename>
bool test_add_sub2(...)
{ return false; }
int main()
{
std::cout << std::boolalpha;
std::cout << "test_add<int>(0) " << test_add<int>(0) << '\n';
std::cout << "test_add<A>(0) " << test_add<A>(0) << '\n';
std::cout << "test_add<B>(0) " << test_add<B>(0) << '\n';
std::cout << "test_add<C>(0) " << test_add<C>(0) << '\n';
std::cout << "test_add_sub<int>(0) " << test_add_sub<int>(0) << '\n';
std::cout << "test_add_sub<A>(0) " << test_add_sub<A>(0) << '\n';
std::cout << "test_add_sub<B>(0) " << test_add_sub<B>(0) << '\n';
std::cout << "test_add_sub<C>(0) " << test_add_sub<C>(0) << '\n';
std::cout << "test_add2<int>(0) " << test_add2<int>(0) << '\n';
std::cout << "test_add2<A>(0) " << test_add2<A>(0) << '\n';
std::cout << "test_add2<B>(0) " << test_add2<B>(0) << '\n';
std::cout << "test_add2<C>(0) " << test_add2<C>(0) << '\n';
std::cout << "test_add_sub2<int>(0) " << test_add_sub2<int>(0) << '\n';
std::cout << "test_add_sub2<A>(0) " << test_add_sub2<A>(0) << '\n';
std::cout << "test_add_sub2<B>(0) " << test_add_sub2<B>(0) << '\n';
std::cout << "test_add_sub2<C>(0) " << test_add_sub2<C>(0) << '\n';
return 0;
}
代码:
#include <iostream>
using std::nullptr_t;
template<typename... T>
using nullptr_vt = nullptr_t;
struct not_addable{};
template<
typename T,
nullptr_vt<decltype(std::declval<T>() + std::declval<T>())> TSfinae = nullptr>
bool test_addable(int)
{ return true; }
template<typename>
bool test_addable(...)
{ return false; }
int main()
{
std::cout << std::boolalpha;
std::cout << test_addable<int>(0) << std::endl;
std::cout << test_addable<not_addable>(0) << std::endl;
// Gives error ("invalid operands to binary expression"):
// nullptr_vt<decltype(std::declval<not_addable>() + std::declval<not_addable>())> a{};
}
我认为这会打印:
true
false
,但事实并非如此。它打印:
true
true
。至少 https://repl.it/@Hrle/sfinaetemplatesuccess。
我认为来自第一个重载的 nullptr_vt<decltype(std::declval<T>() + std::declval<T>())>
对 not_addable
来说是一个错误,它会从重载集中丢弃它,从而选择第二个重载。
如果有默认值,编译器是否能够丢弃 TSfinae
的类型?
I thought that
nullptr_vt<decltype(std::declval<T>() + std::declval<T>())>
from the first overload would be an error for not_addable and it would discard it from the overload set, thus choosing the second overload.
这个想法其实很好,问题是GCC和nullptr_vt
这一行:
nullptr_vt<decltype(std::declval<T>() + std::declval<T>())> TSfinae = nullptr
在 GCC 10.2 上你不希望它工作的地方工作,但在 Clang 11.0.1 上是正确的。将其更改为
nullptr_vt<decltype(std::declval<T>() + std::declval<T>())> *TSfinae = nullptr
两者都是正确的,更简单的
typename TSfinae = nullptr_vt<decltype(std::declval<T>() + std::declval<T>())>
typename _ = decltype(std::declval<T>() + std::declval<T>())
最后 make_void 把戏
template<typename... T> struct make_nullptr_vt { using type = nullptr_t; };
template<typename T>
using nullptr_vt = typename make_nullptr_vt<T>::type;
也在 GCC 上修复了原始版本。
这并没有说明问题,也没有假装比@Useless 的答案更好,但它是我觉得方便的替代解决方案。
我把typename
换成了一个整数,为了省点写,必要时用逗号运算符来枚举很多条件。
当然,当必须多次使用相同的条件时,带有 using
的别名声明可以帮助提高可读性。
编辑
正如@StoryTeller 评论所建议的,如果我们声明一个 operator,
与最后一个 1
组合,那么 1
将被消耗,我们可以在 decltype()
一种会使 SFINAE 失败的类型。
他建议在 1
之前的条件序列中插入 void()
。
实际上,没有右侧操作数是不可能声明 operator,
的;因此,没有任何东西与此 void()
结合,最终 1
将在 decltype()
中发出。
它不像 1
那样最小,但它更安全。
/**
g++ -std=c++17 -o prog_cpp prog_cpp.cpp \
-pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
-g -O0 -UNDEBUG -fsanitize=address,undefined
**/
#include <iostream>
struct A
{
A operator+(A r);
A operator-(A r);
A operator,(int r); // try to mislead SFINAE
};
struct B
{
B operator+(B r);
// no -
};
struct C
{
// no +
// no -
};
template<
typename T,
decltype((std::declval<T>()+std::declval<T>()),
void(),1) =1>
bool test_add(int)
{ return true; }
template<typename>
bool test_add(...)
{ return false; }
template<
typename T,
decltype((std::declval<T>()+std::declval<T>()),
(std::declval<T>()-std::declval<T>()),
void(),1) =1>
bool test_add_sub(int)
{ return true; }
template<typename>
bool test_add_sub(...)
{ return false; }
template<typename T>
using has_add =
decltype((std::declval<T>()+std::declval<T>()),
void(),1);
template<typename T>
using has_add_sub =
decltype((std::declval<T>()+std::declval<T>()),
(std::declval<T>()-std::declval<T>()),
void(),1);
template<
typename T,
has_add<T> =1>
bool test_add2(int)
{ return true; }
template<typename>
bool test_add2(...)
{ return false; }
template<
typename T,
has_add_sub<T> =1>
bool test_add_sub2(int)
{ return true; }
template<typename>
bool test_add_sub2(...)
{ return false; }
int main()
{
std::cout << std::boolalpha;
std::cout << "test_add<int>(0) " << test_add<int>(0) << '\n';
std::cout << "test_add<A>(0) " << test_add<A>(0) << '\n';
std::cout << "test_add<B>(0) " << test_add<B>(0) << '\n';
std::cout << "test_add<C>(0) " << test_add<C>(0) << '\n';
std::cout << "test_add_sub<int>(0) " << test_add_sub<int>(0) << '\n';
std::cout << "test_add_sub<A>(0) " << test_add_sub<A>(0) << '\n';
std::cout << "test_add_sub<B>(0) " << test_add_sub<B>(0) << '\n';
std::cout << "test_add_sub<C>(0) " << test_add_sub<C>(0) << '\n';
std::cout << "test_add2<int>(0) " << test_add2<int>(0) << '\n';
std::cout << "test_add2<A>(0) " << test_add2<A>(0) << '\n';
std::cout << "test_add2<B>(0) " << test_add2<B>(0) << '\n';
std::cout << "test_add2<C>(0) " << test_add2<C>(0) << '\n';
std::cout << "test_add_sub2<int>(0) " << test_add_sub2<int>(0) << '\n';
std::cout << "test_add_sub2<A>(0) " << test_add_sub2<A>(0) << '\n';
std::cout << "test_add_sub2<B>(0) " << test_add_sub2<B>(0) << '\n';
std::cout << "test_add_sub2<C>(0) " << test_add_sub2<C>(0) << '\n';
return 0;
}