Search code examples
c++templatesc++17conditional-operator

Ternary operator not evaluated at compile time -- Template Metaprogramming


template <int T, typename V>
struct Get;

template <int T>
struct Get<T, Vector<>> {
    static_assert(std::false_type::value, "Vector Index out of bounds!");
};

template <int T, int X, int... Xs>
struct Get<T, Vector<X, Xs...>> {
    static const int value = (T == 0) ? X : Get<T - 1, Vector<Xs...>>::value;
};

// ^ Your code goes here

static_assert(Get<0, Vector<0,1,2>>::value == 0);

I was trying this code to get a specific element of a vector. However, why do i get an error here as follows:

p1.cpp: In instantiation of ‘struct Get<-3, Vector<> >’:
p1.cpp:380:69:   recursively required from ‘const int Get<-1, Vector<1, 2> >::value’
p1.cpp:380:69:   required from ‘const int Get<0, Vector<0, 1, 2> >::value’
p1.cpp:385:38:   required from here
p1.cpp:375:40: error: static assertion failed: Vector Index out of bounds!
  375 |         static_assert(std::false_type::value, "Vector Index out of bounds!");
      |                       ~~~~~~~~~~~~~~~~~^~~~~
p1.cpp:375:40: note: ‘std::integral_constant<bool, false>::value’ evaluates to false
p1.cpp: In instantiation of ‘const int Get<-2, Vector<2> >::value’:
p1.cpp:380:69:   recursively required from ‘const int Get<-1, Vector<1, 2> >::value’
p1.cpp:380:69:   required from ‘const int Get<0, Vector<0, 1, 2> >::value’
p1.cpp:385:38:   required from here
p1.cpp:380:76: error: ‘value’ is not a member of ‘Get<-3, Vector<> >’
  380 |         static const int value = (T == 0) ? X : Get<T - 1, Vector<Xs...>>::value;

Why is the ternary operator not evaluated properly, leading to T becoming -3?


Solution

  • Even if the condition of the ternary operator is known at compile-time, all operands still need to be valid and follow the normal odr-use rules. Get<T - 1, Vector<Xs...>>::value must still be verified to be a valid expression, implying that Get<T - 1, Vector<Xs...>> will be instantiated.

    So you will keep on recursively instantiating Get<T - 1, Vector<Xs...>> with T and Xs... becoming smaller and smaller, until you match your first partial specialization, which is then also instantiated and that instantiation fails for triggering the static_assert.

    To avoid the instantiation, you should declare a partial specialization instead of using the ternary operator:

    template <int T, int X, int... Xs>
    struct Get<T, Vector<X, Xs...>> {
        static const int value = Get<T - 1, Vector<Xs...>>::value;
    };
    
    template <int X, int... Xs>
    struct Get<0, Vector<X, Xs...>> {
        static const int value = X;
    };
    

    Also, generally it would be better to use constexpr instead of const for value.


    Also, until relatively recently, a static_assert that unconditionally fails independently of the template parameters, was IFNDR (ill-formed, no diagnostic required), meaning that the compiler was allowed to diagnose it and fail to compile it, regardless of whether it is ever instantiated.

    That was changed only with CWG 2518. So static_assert(std::false_type::value, "Vector Index out of bounds!"); would not be guaranteed to behave in the way you want.

    For detailed explanation and workarounds, see e.g. P2593.