I am trying to write a class template that chooses to store a member variable raw or as a pointer depending on a MAX_SIZE template argument. When I try to instantiate it with a MAX_SIZE large enough that it chooses raw, the compiler still tries to compile the line where I assign the value as a pointer and triggers an impossible conversion error - even though it would probably optimize this line out at some point.

template <int MAX_SIZE = sizeof(void*)>
class WeakVar {
    typedef typename std::conditional<sizeof(long double) <= MAX_SIZE, long double, long double*>::type TypeLD;
    TypeLD ld_;

public:
    WeakVar() {
        if(std::is_pointer<TypeLD>::value)
            ld_ = new long double(0); // error: cannot convert 'long double*' to 'TypeLD'{aka 'long double'}
        else
            ld_ = 0;
    }
    //...
};

The class is meant to be a space-efficient "weak typed variable" (not necessarily speed-efficient, it's not meant to be called often). The 'ld_' member is actually part of a union (along with a char, an int, a bool, a float, etc...).

I tried making setters with std::enable_if, but to no avail...

//...
WeakVar() { setLD(0); }

typename std::enable_if<std::is_pointer<TypeLD>::value>::type setLD(long double value) {
    ld_ = new long double(value);
}
typename std::enable_if<!std::is_pointer<TypeLD>::value>::type setLD(long double value) {
    ld_ = value;
}
// error: the second method cannot be overloaded with the first
//...

Is there a way to achieve this? (while keeping the possibility to choose the MAX_SIZE of the class)

3 Answers

2
max66 On Best Solutions

The problem is that when you write

WeakVar() {
    if(std::is_pointer<TypeLD>::value)
        ld_ = new long double(0); // error: cannot convert 'long double*' to 'TypeLD'{aka 'long double'}
    else
        ld_ = 0;
}

the compiler has to compile both cases of the if()

So also when a TypeLD isn't a pointer, the compiler has to compile

 ld_ = new long double(0);

Solution: if (when) you can use C++17 or newer, use if constexpr

    if constexpr (std::is_pointer<TypeLD>::value)
        ld_ = new long double(0);
    else
        ld_ = 0;

that is introduced exactly to not compile the wrong code when the value of the test is compile-time known.

Otherwise (in C++11 and C++14) you can write two different functions and, using SFINAE, enable the correct one.

By example (caution: code not tested)

template <typename T = TypeLD,
          typename std::enable_if<true == std::is_pointer<T>::value, bool>::type = true>
WeakVar () : ld_{new long double(0)}
 { }

template <typename T = TypeLD,
          typename std::enable_if<false == std::is_pointer<T>::value, bool>::type = true>
WeakVar () : ld_{0}
 { }

I tried making setters with std::enable_if, but to no avail...

This is because SFINAE, to enable/disable a method of a class, works only with template methods with tests over template parameters of the method itself, not over template parameter of the class.

So, in the example above, I've written

template <typename T = TypeLD,
          typename std::enable_if<true == std::is_pointer<T>::value, bool>::type = true>

so the enabling/disabling test is about a typename T, template parameter of the constructor, not about TypeLD, template parameter of the full class.

If you write

template <typename std::enable_if<true == std::is_pointer<TypeLD>::value, bool>::type = true>

you get an error.

1
DeiDei On

A much simpler solution would be to provide a different base class differentiated by std::conditional:

class WeakVarA {};
class WeakVarB {};

template <int MAX_SIZE = sizeof(void*)>
class WeakVar 
    : public typename std::conditional<sizeof(long double) <= MAX_SIZE, WeakVarA, WeakVarB>::type 
{
    // ...
};

Then simply implement WeakVarA as a dynamic and WeakVarB as a non-dynamic approach or vice-versa.

1
Yakk - Adam Nevraumont On

This little utility:

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

template<class S, class F0, class...Fs>
auto dispatch( std::integral_constant<S, S(0)>, F0&& f0, Fs&&... )
RETURNS( dispatch( std::forward<F0>(f0) ) )

template<class S, S s, class F0, class...Fs>
auto dispatch( std::integral_constant<S, s>, F0&&, Fs&&...fs )
RETURNS( dispatch( std::integral_constant<S, S(s-1)>{}, std::forward<Fs>(fs)... ) )

template<std::size_t N, class...Fs>
auto dispatch( Fs&&...fs )
RETURNS( dispatch( std::integral_constant<std::size_t, N>{}, std::forward<Fs>(fs)... ) )

can help.

It does a compile-time switch.

WeakVar() {
    ld_ = dispatch(std::is_pointer<TypeLD>{}, []{ return 0.; }, []{ return new long double(0); } )();
}

dispatch, when called with a compile-time constant std::integral_constant<T, t>, returns its nth argument. If you pass it std::true_type, that is std::integral_constant<bool, true>.

std::is_pointer<T> inherits from either true_type or false_type.

We then pass dispatch 2 lambdas. At compile time one is picked. We then run the return value. It then returns either a double, or a pointer to a double.

As which one is returned is determined at compile time, this works fine.

In and this gets much easier, but I work with what I got. And dispatch works really well to solve this kind of problem in .