Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::conditional compile-time branch evaluation

Compiling this:

template < class T, class Y, class ...Args >
struct isSame
{
    static constexpr bool value = std::conditional<
                                      sizeof...( Args ),
                                      typename std::conditional<
                                          std::is_same< T, Y >::value,
                                          isSame< Y, Args... >, // Error!
                                          std::false_type >::type,
                                      std::is_same< T, Y > >::type::value;
};

int main()
{
    qDebug() << isSame< double, int >::value;
    return EXIT_SUCCESS;
}

Gives me this compiler error:

error: wrong number of template arguments (1, should be 2 or more)

The issue is that isSame< double, int > has an empty Args parameter pack, so isSame< Y, Args... > effectively becomes isSame< Y > which does not match the signature.

But my question is: Why is that branch being evaluated at all? sizeof...( Args ) is false, so the inner std:conditional should not be evaluated. This isn't a runtime piece of code, the compiler knows that sizeof..( Args ) will never be true with the given template types.

If you're curious, it's supposed to be a variadic version of std::is_same, not that it works...

like image 519
cmannett85 Avatar asked Jun 07 '14 14:06

cmannett85


3 Answers

But my question is: Why is that branch being evaluated at all?

Because there is no evaluation, and thats not a branch at all. Thats a template and its instanced to successfully instance the std::conditional template.
What I mean here is that the evaluation abstraction is usefull when writting template metaprograms, but you should never forget what the template system is really doing.

If you need to conditionally instantiate a potentially ill-formed template, add a level of indirection. Check this answer for an example of that.

like image 168
Manu343726 Avatar answered Oct 03 '22 20:10

Manu343726


You have an error because the type has to be correct when used as template parameter.
You may use template specialization to solve your issue, something like:

#include <type_traits>

template <typename ... Ts> struct are_same;

template <> struct are_same<> : std::true_type {};
template <typename T> struct are_same<T> : std::true_type {};

template <typename T1, typename T2, typename... Ts>
struct are_same<T1, T2, Ts...> :
    std::conditional<
        std::is_same<T1, T2>::value,
        are_same<T2, Ts...>,
        std::false_type
    >::type
{};

static_assert(are_same<char, char, char>::value, "all type should be identical");
static_assert(!are_same<char, char, int>::value, "all type should not be identical");
like image 31
Jarod42 Avatar answered Oct 03 '22 20:10

Jarod42


Don't blame std::conditional

In short it's because the template argument substitution happens outside of std::conditional, which means that it's not std::conditional that is causing the error.. it's isSame.

Imagine std::conditional being a function call (which it isn't), what we pass as parameters are evaulated before the actual body of the function is evaluated, and what we pass as parameters sure must be a valid construct, even if the function itself doesn't use them.

Further reading is available in the below linked answer:

  • Improper usage of std::conditional

A solution

Add some indirection so that you don't instantiate isSame if sizeof... (Args) == 0, you could have a traited used as isSame_if_not_empty<Args..>::type which would yield isSame<Args...> if Args is not empty, and something else if it is indeed empty.


Proposed solution

Fix so that isSame can be used with an empty variadic pack, yielding true, this is the sensible approach. If the description of isSame is "all types passed are of the same type", an empty pack sure has "all of its types" of the same type.

like image 40
Filip Roséen - refp Avatar answered Oct 03 '22 19:10

Filip Roséen - refp