Understanding SFINAE - c++

As far as I know, SFINAE means substitution failures do not result in compilation errors, but just remove the prototype from the list of possible overloads.
What I do not understand: why is this SFINAE:
template <bool C, typename T = void> struct enable_if{};
template <typename T> struct enable_if<true, T> { typedef T type; };
But this is not?
template <bool C> struct assert;
template <> struct assert<true>{};
From my understanding, the underlying logic is identical here. This question emerged from the comments to this answer.

In C++98, SFINAE is done with either a return type or a function's dummy argument with default parameter
// SFINAE on return type for functions with fixed arguments (e.g. operator overloading)
template<class T>
typename std::enable_if< std::is_integral<T>::value, void>::type
my_function(T const&);
// SFINAE on dummy argument with default parameter for functions with no return type (e.g. constructors)
template<class T>
void my_function(T const&, std::enable_if< std::is_integral<T>::value, void>::type* = nullptr);
In both cases, substution of T in order to get the nested type type is the essence of SFINAE. In contrast to std::enable_if, your assert template does not have a nested type that can be used in substitution part of SFINAE.
See Jonathan Wakely's excellent ACCU 2013 presentation for more details and also for the C++11 expression SFINAE. Among others (as pointed out by #BartekBanachewicz in the comments) is is now also possible to use SFINAE in function template default arguments
// use C++11 default function arguments, no clutter in function's signature!
template<class T, class dummy = typename std::enable_if< std::is_integral<T>::value, void>::type>
void my_function(T const&);

Related

Can we have variadic concepts before variadic template parameters?

Can we have variadic concepts before variadic template parameters?
I meant, is following legal:
template <class, std::size_t> concept Any = true;
template <class> struct n_ary;
template <std::size_t... Is>
struct n_ary<std::index_sequence<Is...>>
{
template <Any<Is>... Ls, typename ... Ts>
void operator()(Ls..., Ts...) {}
};
Demo (accepted only by clang)
Note: Without the extra Ts, it is accepted by all compilers Demo.
This is probably in violation of [temp.param] section 14:
... A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list ([dcl.fct]) of the function template or has a default argument ([temp.deduct]). ...
It includes this example:
template<class T1 = int, class T2> class B; // error
// U can be neither deduced from the parameter-type-list nor specified
template<class... T, class... U> void f() { } // error
template<class... T, class U> void g() { } // error
However, cases like f(T..., U...) are currently accepted by compilers, which see one (or both) parameter packs as empty.
By the the example in section 5 about type constraints,
template<C3<int>... T> struct s5; // associates (C3<T, int> && ...)
the syntax in your example should be "functionally equivalent, but not equivalent" to template<typename... Ls, typename... Ts> requires (Any<Ls,Is> && ...). The expansion rules [temp.variadic] require that Ls and Is have the same size, which is why gcc and MSVC ultimately reject the call in your example. Interestingly, if you use the functionally equivalent syntax, clang does join the other compilers in rejecting it.

Different member function definition according to compile-time condition

As per this answer, I've been using
template <typename T,
typename = typename enable_if<bool_verfier<T>()>::type> >
classMember(const T& arg);
As the function signature for several class members, where bool_verifier<T>() is a templated function that asserts that a particular class T fulfills certain requirements, with return type constexpr bool. This ensures a particular overload of classMember(const T& arg) is only used for particular argument types, but it is not possible to do this when there are multiple overloads with the same prototype/argument signature, because the compiler won't allow it:
// ...
template <typename T, typename = typename enable_if<bool_verfier<T>()>::type> >
classMember(const T& arg);
template <typename T, typename = typename enable_if<!(bool_verfier<T>())>::type>>
classMember(const T& arg);
// ...
which causes the following compilation error:
‘template<class T, class> void myClass::classMember<T>(const T&)’
cannot be overloaded with
‘template<class T, class> void std::myClass<T>::classMember(const T&)’
If I need classMember to have different definitions according to whether or not bool_verifier<T>() returns true, what would be the correct syntax/member declaration? Alternatively, is there a way to call bool_verifier<T> from an #if precompiler conditional statement?
Alternatively, is there a way to call bool_verifier<T> from an #if precompiler conditional statement?
Nope. The preprocessor runs before anything else, and doesn't have knowledge of C++ at all.
You probably need to disambiguate between the two overloads with an extra template parameter (or by changing where enable_if appears), as default template parameter values are not part of the signature. The following works for me:
struct foo
{
template <typename T, typename = std::enable_if_t<bool_verifier<T>{}>>
void a();
template <typename T, typename = std::enable_if_t<!bool_verifier<T>{}>, typename = void>
void a();
};
live godbolt.org link

disable template member function if return type is an array

https://www.godbolt.org/z/_4aqsF:
template <typename T> struct Container
{
template <typename TPred> T find_if(TPred pred); // the culprit
};
template <typename T> Container<T> MakeContainer(T const &)
{
return Container<T>();
}
int main()
{
auto x = MakeContainer("Hello!");
}
gcc, clang and msvc apparently agree that this cannot compile because find_if would return an array.
(I would have assumed that the member template isn't instantiated since it doesn't get used - apparently, this simplistic view is wrong.)
Why does SFINAE not apply here?
Is there a way to exclude the member template for types where T is not a returnable type?
SFINAE is not in play because the members of the types produced in your MakeContainer return point are not examined during SFINAE of MakeContainer overloads.
SFINAE happens only in an immediate context. Bodies of types and functions are not in-scope and do not cause a subsitution failure.
template <typename T=char[7]> Container<char[7]> MakeContainer(char const (&)[7])
this signature is fine.
Once selected, the Container<char[7]> is instantiated and its methods parsed.
template <typename TPred> char[7] find_if(TPred pred); // the culprit
there is no TPred that could cause this find_if to be a valid method, so your program is ill formed no diagnostic required.
The correct fix is:
template <typename T> struct Container
{
template <typename TPred> T find_if(TPred pred); // the culprit
};
template <class T, std::size_t N> struct Container<T[N]>:
Container<std::array<T,N>>
{
using Container<std::array<T,N>>::Container;
};
of course, Container<std::array<T,N>> itself needs a very special find_if and probably constructors. But at least it doesn't break immediately.
SFINAE removes from overload sets the overloads which would be illegal during template argument deduction.
Here, the overload set contains only one candidate: MakeContainer<const char (&)[7]>. Template argument deduction ends here. No ambiguity. Everything's fine.
Then, the type Container<const char (&)[7]> is instantiated. And it produces a templated function (Container<const char (&)[7]>::find_if) whose signatures are illegal (all of them, since T is deduced in the context of find_if). SFINAE is not at play.
Now, you can add some SFINAE to your container's find_if function by making its return type depend on its template argument. For that, see max66's answer for that.
To use SFINAE on fidn_if you need to use dependent parameters of the function itself, here's a version that SFINAE on non-returnable types:
template <typename TPred, class U = T, typename std::enable_if<
std::is_same<T, U>::value
&& !std::is_abstract<U>::value
&& !std::is_function<U>::value
&& !std::is_array<U>::value
, bool>::type = true>
U find_if(TPred pred);
Try with
template <typename TPred, typename U = T>
U find_if (TPred pred); // the culprit
SFINAE, over methods, doesn't works with templates parameters of the class. Works over templates of the method itself. So you have to make the SFINAE substitution dependant from a template parameter of the method itself.
So not T but U.
If you fear that someone can "hijack" your function explicating the template types as follows
auto x = MakeContainer("Hello!");
x.find_if<int, int>(1);
you can impose that U and T are the same type
template <typename TPred, typename U = T>
typename std::enable_if<std::is_same<U, T>::value, U>::type
find_if (TPred pred) // the culprit

Universal reference with templated class

Example:
template <typename T>
class Bar
{
public:
void foo(T&& arg)
{
std::forward<T>(arg);
}
};
Bar<int> bar;
bar.foo(10); // works
int a{ 10 };
bar.foo(a); // error C2664: cannot convert argument 1 from 'int' to 'int &&'
It seems that universal references works only with templated functions and only with type deduction, right? So it make no sense to use it with class? And does using of std::forward makes sense in my case?
Note that the preferred terminology (i.e. the one which will be in future versions of the spec) is now forwarding reference.
As you say, a forwarding reference only works with type deduction in a function template. In your case, when you say T&&, T is int. It can't be int& because it has been explicitly stated in your Bar instantiation. As such, reference-collapsing rules can't occur, so you can't do perfect forwarding.
If you want to do perfect forwarding in a member function like that, you need to have a member function template:
template <typename U>
void foo(U&& arg)
{
std::forward<U>(arg); //actually do something here
}
If you absolutely need U to have the same unqualified type as T, you can do a static_assert:
template <typename U>
void foo(U&& arg)
{
static_assert(std::is_same<std::decay_t<U>,std::decay_t<T>>::value,
"U must be the same as T");
std::forward<U>(arg); //actually do something here
}
std::decay might be a bit too aggressive for you as it will decay array types to pointers. If that's not what you want, you could write your own simple trait:
template <typename T>
using remove_cv_ref = std::remove_cv_t<std::remove_reference_t<T>>;
template <typename T, typename U>
using is_equiv = std::is_same<remove_cv_ref<T>, remove_cv_ref<U>>;
If you need a variadic version, we can write an are_equiv trait. First we need a trait to check if all traits in a pack are true. I'll use the bool_pack method:
namespace detail
{
template<bool...> struct bool_pack;
template<bool... bs>
using all_true = std::is_same<bool_pack<bs..., true>, bool_pack<true, bs...>>;
}
template <typename... Ts>
using all_true = detail::all_true<Ts::value...>;
Then we need something to check if each pair of types in Ts... and Us... satisfy is_equiv. We can't take two parameter packs as template arguments, so I'll use std::tuple to separate them (you could use a sentinel node, or split the pack halfway through instead if you wanted):
template <typename TTuple, typename UTuple>
struct are_equiv;
template <typename... Ts, typename... Us>
struct are_equiv <std::tuple<Ts...>, std::tuple<Us...>> : all_true<is_equiv<Ts,Us>...>
{};
Then we can use this like:
static_assert(are_equiv<std::tuple<Ts...>,std::tuple<Us...>>::value,
"Us must be equivalent to Ts");
You're right : "universal references" only appear when the type of a deduced parameter is T&&. In your case, there is no deduction (T is known from the class), hence no universal reference.
In your snippet, std::forward will always perform std::move as arg is a regular rvalue reference.
If you wish to generate a universal reference, you need to make foo a function template :
template <typename T>
class Bar
{
public:
template <typename U>
void foo(U&& arg)
{
std::forward<U>(arg);
}
};

Template template functions and parameters deduction

I've got a problem with template templates and parameters deduction. Here's the code:
template<typename U, template<typename> class T>
void test(T<U>&& t)
{
...
}
I expected this to accept either lvalues and rvalues, but only works with rvalues. The collapsing rule "T& && = T&" doesn't apply in this case?
Naturally I could declare the lvalue reference function too, but makes the code less readable.
If you're asking why I need this is to use a static_assert to check if T is a particular class. If there's a simpler way to do so I'll be happy to change my code, but I'd like to know if template templates are usable in this way.
Thanks
Unlike typename T, which can be deduced to be a reference type, template<typename> class T can only ever be deduced to be a class template, so T<U> is always deduced to an object type.
You can write your function templated on T then unpack the template type in the static_assert:
template<typename T> struct is_particular_class: std::false_type {};
template<typename U> struct is_particular_class<ParticularClass<U>>: std::true_type {};
template<typename T> void test(T &&) {
static_assert(is_particular_class<std::remove_reference<T>::type>::value, "!");
}