This doesn't explain the problem, and it does not pretend to be better than @Useless answer, but it is an alternative solution I find convenient.
I replace the typename
by an integer in order to save a bit of writing, and use the comma operator in order to enumerate many conditions if necessary.
Of course, an alias declaration with using
can help increase readability when the same conditions have to be used many times.
EDIT
As suggested by @StoryTeller comment, if we declare an operator,
that combines with the last 1
, then that 1
will be consumed and we can emit instead in decltype()
a type that will make SFINAE fail.
He suggests inserting a void()
in the sequence of conditions just before the 1
.
Actually, it is not possible to declare an operator,
without a right-hand-side operand; thus nothing will combine with this void()
and finally 1
will be emitted in decltype()
.
It's not as minimal as just 1
, but it's safer.
/**
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;
}
true false
godbolt.org/z/KjdecT – Pachalicdecltype(std::declval<T>() + std::declval<T>())> TSfinae = nullptr
todecltype(std::declval<T>() + std::declval<T>())>* TSfinae = nullptr
it works... – Recording