Home > Net >  `std::enable_if` instantiating type parameter when predicted has failed causing error
`std::enable_if` instantiating type parameter when predicted has failed causing error

Time:02-21

I have a function that I want to toggle on and off using std::enable_if on its return type. Like so:

#include <utility>
#include <type_traits>

template<typename T>  
auto bob(T v) {
    return v.size;
    //return 0;
}

template<typename T>
struct the_type {
    using type = decltype( bob( std::declval<T>() )   )>; 
};

template<int I>
auto foo() -> std::enable_if_t<I != 0, typename the_type<int>::type> {
    return {}; 
}

template<int I>
auto foo() -> std::enable_if_t<I == 0, int> {
    return 5;
}

int main() {
    foo<0>();
}

However the problem I am having is that even when the predicate of std::enable_if is false it still tries to instantiate the type parameter, which in turn invokes another function using decltype. That function does not work with specified type.

Please note the above code is a test case that demostrates the same problem I am having with a much large code base.

I've tried adding in std::conditional_t but that like std::enable_if tries to instantiate both of its template parameters.

The behaviour I'd like is for the compiler to recognise that 0 != 0 is false, and either not instantiate the type parameter or simply ignore any compile errors.

How can I resolve this? Thanks in advance.

CodePudding user response:

If you put the enable_if in a different place it will work better:

template<int I, typename = std::enable_if_t<I != 0>>
typename the_type<int>::type foo()

CodePudding user response:

Ok, I've found a work around, but I'd still be keen to hear other ideas, as this feels crusty:

I can change the_type to the following:

template<typename T, bool = std::is_same_v<T, int>>
struct the_type {
    using type = decltype( bob( std::declval<T>() )   );  
};


template<typename T>
struct the_type<T, true> {
    using type = std::nullptr_t;
};

Note in the real code, std::is_same is replace by a more complex predicate - using std::is_same to demonstrate the behaviour.

In an ideal world, I wouldn't need to provide the predicate, perhaps the failure of bob would be enough for it to try something else.

CodePudding user response:

Your have several similar issue:

  • bob is not SFINAE friendly. i.e bob<int> produce an hard failure (failure happens in the body, not in declaration).
  • the_type is not SFINAE friendly neither but anyway the_type<int>::type doesn't depend of template parameter (so error doesn't come from substitution).

You can delay instantiation to avoid hard error

template<typename T>  
auto bob(T v) { return v.size; }

template <typename T>
struct the_type {
    using type = decltype( bob( std::declval<T>() )   )>; 
};

template<int I>
auto foo()
-> typename std::conditional_t<I == 0,
                               std::type_identity<int>,
                               the_type<int>
                              >::type
{
    if constexpr (I == 0) {
        return 5;
    } else {
        return {};
        // Following would still be an hard error (independent of I)
        // return typename the_type<int>::type{}; 
    }
}

Demo Notice that foo<1> would still be an hard error.

Or make all type/function SFINAE friendly:

template<typename T>  
auto bob(T v) -> decltype(v.size)
{
    return v.size;
}

template<typename T, typename Enabler = void>
struct the_type;

template<typename T>
struct the_type<T, std::void_t<decltype( bob( std::declval<T>() )   )>>
 {
    using type = decltype( bob( std::declval<T>() )); 
};

template <int I, typename T>
struct always
{
    using type = T;
};

template <int I, typename T>
using always_t = typename always<I, T>::type;

template<int I>
auto foo()
-> std::enable_if_t<I != 0, typename the_type<always_t<I, int>>::type> {
    return {}; 
}

template<int I>
auto foo() -> std::enable_if_t<I == 0, int> {
    return 5;
}

Demo

  • Related