Lynden Shields Lynden Shields - 1 month ago 10
C++ Question

Cross platform selection of operator overload by class template parameter

I'm trying to write a templated class which is essentially just a wrapper for other types for the sake of being able to easily watch things that are happening to the values - every time any operator, constructor, destructor, etc, is called.
So I'm trying to make an operator bool() that works by calling operator bool() on any class that has one, static_cast on any class that doesn't have operator bool(). Classes that can't do either should only fail compilation if someone tries to convert to bool. I haven't focused on this part yet, as I'm stumped by this simpler case.

It compiles and works fine on MSVC 2015, but won't compiler on either Clang (3.8.1) or GCC (6.2). GCC and Clang are both set to

-std=c++1z


Link to online compiler showing errors on GCC and Clang: https://godbolt.org/g/v3B6TE

This is a stripped down version that shows the issue:

#include <type_traits>
#include <utility>
#include <assert.h>

template <typename T>
struct Wrapper {
T val;

template<typename...Params>
Wrapper(Params&&...args): val(std::forward<Params>(args)...){}

/**
* \brief bool conversion operator
*/
template <typename = std::enable_if_t<std::is_fundamental<T>::value>>
operator bool() const {
return static_cast<bool>(val);
}

/**
* \brief bool conversion operator
*/
template <typename = std::enable_if_t<!std::is_fundamental<T>::value>, typename = void>
operator bool() const {
return val.operator bool();
}
};


struct HasOperatorBool {
mutable bool done{ false };

operator bool() const{
done = true;
return done;
}
};

int main(int argc, char** argv) {
Wrapper<HasOperatorBool> whob;
bool didIt = whob;
assert(didIt);

Wrapper<int> wi{1};
bool bi = wi;
assert(bi);

return 0;
}


Both clang and gcc are saying something about
std::enable_if_t
having no
type
:

gcc:

In file included from /tmp/gcc-explorer-compiler116910-70-16kehep/example.cpp:1:
/usr/lib/gcc/x86_64-linux-gnu/5.4.1/../../../../include/c++/5.4.1/type_traits:2388:44: error: no type named 'type' in 'std::enable_if<false, void>'; 'enable_if' cannot be used to disable this declaration
using enable_if_t = typename enable_if<_Cond, _Tp>::type;
^~~~~
15 : note: in instantiation of template type alias 'enable_if_t' requested here
template <typename = std::enable_if_t<std::is_fundamental<T>::value>>
^
40 : note: in instantiation of template class 'Wrapper<HasOperatorBool>' requested here
Wrapper<HasOperatorBool> whob;
^
In file included from /tmp/gcc-explorer-compiler116910-70-16kehep/example.cpp:1:
/usr/lib/gcc/x86_64-linux-gnu/5.4.1/../../../../include/c++/5.4.1/type_traits:2388:44: error: no type named 'type' in 'std::enable_if<false, void>'; 'enable_if' cannot be used to disable this declaration
using enable_if_t = typename enable_if<_Cond, _Tp>::type;
^~~~~
2 errors generated.
Compiler exited with result code 1


clang:

In file included from /tmp/gcc-explorer-compiler116910-70-1nlkefa/example.cpp:1:0:
/opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/type_traits: In substitution of 'template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = std::is_fundamental<HasOperatorBool>::value; _Tp = void]':
16 : required from 'struct Wrapper<HasOperatorBool>'
40 : required from here
/opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/type_traits:2512:61: error: no type named 'type' in 'struct std::enable_if<false, void>'
using enable_if_t = typename enable_if<_Cond, _Tp>::type;


I am perhaps interested in alternative approaches to this, but I'm very interested in the technical reason why this won't work with 2 of the 3 compilers. Are one or more of the compilers wrong? Is there a way to achieve what I want?

Answer

You're not actually doing SFINAE. When using std::enable_if<T>, the T in question should come directly from the template that the compiler is trying to instantiate (the immediate-context). But since T is a template parameter of your class, not the method, the substitution fails and gives a hard error instead of SFINAE. I am not sure whether the behaviour of VS is standard-conformant in that respect.

What you want instead is to introduce a dummy template parameter that defaults to the class template parameter:

/**
* \brief bool conversion operator
*/
template <typename X = T, typename = std::enable_if_t<std::is_fundamental<X>::value>>
operator bool() const {
    return static_cast<bool>(val);
}

/**
* \brief bool conversion operator
*/
template <typename X = T, typename = std::enable_if_t<!std::is_fundamental<X>::value>, typename = void>
operator bool() const {
    return val.operator bool();
}