Sfinae Tried with Bool Gives Compiler Error: "Template Argument 'T::Value' Involves Template Parameter"

SFINAE tried with bool gives compiler error: template argument ‘T::value’ involves template parameter

Actually what you're doing is forbidden by section §14.5.4/9 which says,

A partially specialized non-type argument expression shall not involve a template parameter of the partial specialization except when the argument expression is a simple identifier.

The trick could be using a type for second template parameter as well, encapsulating the non-type value, as described below:

template<bool b> struct booltype {};

template<typename T, typename B = booltype<true> >
struct Resolve
{
static const bool value = false;
};

template<typename T>
struct Resolve<T, booltype<T::my_value> >
{
static const bool value = true;
};

Now it compile fines.

Default parameter in template - template argument involves template parameter

On the assumption that IsAoS<T>'s desired behavior is:

  • If IsComplex<T>::value is false, it defaults to false_type;
  • Otherwise, it is an error unless the user provides a specialization.

It's straightforward to implement with a static_assert; no default template argument needed:

template< typename T >
struct IsAoS: std::false_type {
static_assert(!IsComplex<T>::value, "A user specialization must be provided for Complex types");
};

If you want the default argument trickery, just use a wrapper layer:

namespace detail {

template< typename T, bool T_isComplex = IsComplex<T>::value >
struct IsAoS_impl: std::false_type{};

/**
* Undefined for (unknown) complex types
*/
template< typename T >
struct IsAoS_impl< T, true >;
}

template<class T> struct IsAoS : detail::IsAoS_impl<T> {};

Users should just specialize IsAoS.

Is sizeof... allowed in template arguments for specialization?

I'm going to assume this is a compiler issue after reading this post.

A partially specialized non-type argument expression shall not involve a template parameter of the partial specialization except when the argument expression is a simple identifier.

Which is being disputed here.

GCC is either incorrectly unpacking the parameter pack, or evaluating sizeof prematurely.

Response to bug report I filed may be helpful.

How does changing a template argument from a type to a non-type make SFINAE work?

Mainly because [temp.over.link]/6 does not talk about template default argument:

Two template-heads are equivalent if their template-parameter-lists have the same length, corresponding template-parameters are equivalent, and if either has a requires-clause, they both have requires-clauses and the corresponding constraint-expressions are equivalent. Two template-parameters are equivalent under the following conditions:

  • they declare template parameters of the same kind,

  • if either declares a template parameter pack, they both do,

  • if they declare non-type template parameters, they have equivalent types,

  • if they declare template template parameters, their template parameters are equivalent, and

  • if either is declared with a qualified-concept-name, they both are, and the qualified-concept-names are equivalent.

Then by [temp.over.link]/7:

Two function templates are equivalent if they are declared in the same scope, have the same name, have equivalent template-heads, and have return types, parameter lists, and trailing requires-clauses (if any) that are equivalent using the rules described above to compare expressions involving template parameters.

... the two templates in your first example are equivalent, while the two templates in your second example are not. So the two templates in your first example declare the same entity and result in an ill-formed construct by [class.mem]/5:

A member shall not be declared twice in the member-specification, ...

Variadic Variable Template specialization with SFINAE

Let's drop the actual default template argument for a second, and give that last parameter a name.

Your primary variable template looks like this:

template <typename Tag, typename... Tags, typename _Unnamed>
const std::string config_data = ...;

And your first specialization is:

template <>
const std::string config_data<WantName> = ...;

So in this specialization, Tag=WantName, Tags={}, and _Unnamed is... what exactly? It's not specified. Indeed, there's no way to actually specify it. It's fine to have a trailing defaulted template parameter, it's just always going to be defaulted. But once you try to specialize it, it's impossible to do.

In C++20, you'll be able to properly constrain this:

template <DerivedFrom<Want> Tag, DerivedFrom<Want>... Tags>
const std::string config_data = ...;

Until then, do you really need SFINAE? Not entirely sure what you get out of it. If you just drop it, everything works fine.

Why does SFINAE not give the correct result for incrementing bool?

It looks like Clang erroneously permits the incrementation of bool values in not-evaluated contexts.

We can simplify your example using C++20 concepts:

template<class T>
concept is_incrementable = requires(T t) {
{ ++t };
};

int main() {
static_assert( is_incrementable<int> );
static_assert( !is_incrementable<bool> ); // Clang error here
}

This program is accepted in GCC and MSVC, but Clang shows the same wrong behaviour here, demo: https://gcc.godbolt.org/z/YEnKfG8T5

I think it is Clang bug, so submitted a bug report: https://bugs.llvm.org/show_bug.cgi?id=52280

SFINAE doesn't work in recursive function

Your operator() overload is completely unconstrained and therefore claims to be callable with any set of arguments. Only declarations, not definitions, are inspected to determine which function to call in overload resolution. If substitution into the definition then fails, SFINAE does not apply.

So, constrain your operator() to require TFunc to be callable with TArg and TArgs... as arguments.

For example:

template <typename... TArgs>
auto operator()(TArgs ...args) const -> decltype(func(arg, args...))

Why does this substitution failure create an error, again?

// Failed to specialize alias template

For one, there's no alias template in your code.¹ You're just delcaring bIsIntegralType to be exactly the same thing as std::is_integral_v<value_t_arg>, which is fixed (to false or true) as soon as the instantiation of underlyingtype takes place.

Therefore, the two specializations

    template <typename T>
struct IsSpecialType<T, std::enable_if_t<bIsIntegralType>> {
static inline constexpr bool bIsSpecialType = true;
};

// This also creates an error, this is essentially the same as above
template <typename T>
struct IsSpecialType<T, std::enable_if_t<std::is_integral_v<value_t_arg>>> {
static inline constexpr bool bIsSpecialType = true;
};

are the same thing, hence clang says

Class template partial specialization 'IsSpecialType<T>' cannot be redeclared

And this is independent of what value_t_arg you pass to underlyingtype.

When removing either of the two identical specializations, the code is ok as regards underlyingtype<int> g1;, but it is still invalid upon trying to instantiate underlyingtype<double>, because value_t_arg is "blocked" to double in that case, which makes bIsIntegralType be just a false compile-time value, which in turns means that you're passing an always-and-ever-false to std::enable_if_v.

Putting it in another way, when you ask for underlyingtype<double>, the compiler starts instantiating the class underlyingtype with value_t_arg = double; at this point the compiler hasn't even looked at IsSpecialType, but it knows that bIsIntegralType == false, which makes the code for IsSpecialType's specialization invalid as per the previous question.


(¹) An alias template is a templated type alias,

template <typename T>
using new_name = old_name<T>;

whereas in your code there's no using at all, so there couldn't be a type alias, let alone an alias template.


Based on this and the previous question, it looks like you're trying to get into SFINAE and Template Meta-Programming. If I may give you a suggestion, a good way to learn it is to read and understand how the Boost.Hana library works. There's a lot of TMP and SFINAE there, but the quality of the code is high (imho) and the code itself is extremely well documented and, hence, understandable (obviously it takes time).

Class function template definition outside of the class

If you want to make reset() SFINAE-friendly, make it a fake template:

template<bool is_arr = _arr, typename = std::enable_if_t<is_arr>>
void reset();

Note that SFINAE works when a template is instantiated, and the condition should depend on the template parameter. This is not a valid SFINAE construct:

template<typename = typename std::enable_if<!_arr>::type>
void reset();

If you don't care about SFINAE-friendliness, use static_assert() inside reset().

Edit.

Consider the following simple code as a demonstration of valid and invalid SFINAE:

template<class T, bool f = std::is_integral_v<T>>
struct S {
// template<typename = std::enable_if_t<f>> // (invalid)
template<bool _f = f, typename = std::enable_if_t<_f>> // (valid)
void reset() {}
};

template<class T, typename = void>
struct has_reset : std::false_type {};

template<class T>
struct has_reset<T, std::void_t<decltype(std::declval<T>().reset())>> : std::true_type {};

void foo() {
has_reset<S<int>>::value;
has_reset<S<void>>::value;
}

It will fail to compile if your replace the (valid) line with the (invalid) one.

Edit 2.

When you define a member function outside the class, you don't repeat default values of template parameters:

template<class T, bool f>
template<bool, typename>
void S<T, f>::reset() { }

Edit 3.

For some reason (a compiler bug I suppose) MSVC rejects this definition with the error: "Could not deduce template argument for «unnamed-symbol»". It can be fixed by adding a name for the bool parameter:

template<class T, bool f>
template<bool _f, typename>
void S<T, f>::reset() { }

This name should match that in the declaration.



Related Topics



Leave a reply



Submit