Code:
#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{};
}
I thought this would print:
true
false
, but it doesn't. It prints:
true
true
. At least on https://repl.it/@Hrle/sfinaetemplatesuccess.
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.
Does the compiler have the ability to discard the type of TSfinae
if there is a default?
So the simple answer is YES.
Substitution failure is not an error (SFINAE) refers to a situation in C++ where an invalid substitution of template parameters is not in itself an error. David Vandevoorde first introduced the acronym SFINAE to describe related programming techniques.
This rule applies during overload resolution of function templates: When substituting the explicitly specified or deduced type for the template parameter fails, the specialization is discarded from the overload set instead of causing a compile error. This feature is used in template metaprogramming.
SFINAE is a bit like a windmill. It sits as a wart in the middle of an interface, BUT it's useful to create elaborate static polymorphism, in particular before C++17 and if constexpr , and even in some use cases in C++17.
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.
The idea is actually fine, the problem is just with GCC and nullptr_vt
This line:
nullptr_vt<decltype(std::declval<T>() + std::declval<T>())> TSfinae = nullptr
works where you don't want it to on GCC 10.2 but is correct on Clang 11.0.1. Changing it to
nullptr_vt<decltype(std::declval<T>() + std::declval<T>())> *TSfinae = nullptr
is correct on both, as are the simpler
typename TSfinae = nullptr_vt<decltype(std::declval<T>() + std::declval<T>())>
typename _ = decltype(std::declval<T>() + std::declval<T>())
And finally the make_void trick
template<typename... T> struct make_nullptr_vt { using type = nullptr_t; };
template<typename T>
using nullptr_vt = typename make_nullptr_vt<T>::type;
fixes the original version on GCC as well.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With