0

I am defining a way to know the position of the type in a type list, using recursive templates in C++17. I tried two ways : one using constexpr value and one using constexpr function. The second, using if statement, compiles, while the first, using ternary operator, does not compile.

#include <type_traits>
#include <iostream>

template<typename Searching, typename First, typename...Others>
constexpr std::size_t index_of = std::is_same_v<Searching,First> ? 0 : 1 + index_of<Searching,Others...>;

template<typename Searching, typename First, typename...Others>
constexpr std::size_t IndexOf() {
    if constexpr(std::is_same_v<Searching,First>)
        return 0;
    else return 1 + IndexOf<Searching,Others...>();
};

int main() {
    std::cout << index_of<int, int> << std::endl; //does not compile
    std::cout << IndexOf<int, int>() << std::endl; //compile
    //both should return 0
    return 0;
}

My compiler, migw64, says :

wrong number of template arguments (1, should be at least 2)
constexpr std::size_t index_of = std::is_same_v<Searching,First> ? 0 : 1 + index_of<Searching,Others...>;

From what I understand, the ternary operator needs to evaluate its two operands, so it can not be used in this type of recursion.

Am I right ? and if yes, why is it like that ?
Thank you.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
Julien Vernay
  • 295
  • 3
  • 13
  • 3
    `index_of` takes two or more parameters but on the line in question you are only passing it one or more parameters.........(which means that in some circumstances you are only passing it one parameter) – DarthRubik Jun 23 '18 at 20:13
  • @DarthRubik - you should, IMHO, transform your comment in an answer. – max66 Jun 23 '18 at 20:29
  • @DarthRubik "in some circumstances" is in the recursive definition of `index_of` with the ternary operator, IMO : the compilation error should occur only if `is_same_v` is false – Julien Vernay Jun 23 '18 at 20:32

2 Answers2

1

I'm going to start at the end of the question, then work up.

From what I understand, the ternary operator needs to evaluate its two operands

No. The ternary (meaning "made of three") operator has three operands, not two. When evaluating this operator, two of the three operands are evaluated: the condition and whichever operand the condition picks.

Evaluation is not where your problem lies.

the first, using ternary operator, does not compile.

I think I see why this is. You are assigning the result of the conditional operator to a std::size_t. In order for this to compile, the type of this result must be std::size_t or convertible to that type. So the compiler needs to determine the type of the result. I found rules for determining the type. The first rule applies if either the second or third operands has type void. So even though one of these operands will not be evaluated, both of their types must be known.

OK, so what is the type of your third operand, the one that will not be evaluated? Well, it's 1 + index_of<int>, so we better check the declaration of index_of<int>. Oops, we need two parameters. Cue error message.

This is probably something you'd have to deal with anyway, as you should get the same error for either approach in the "not found" case (for example: index_of<unsigned, int, long, float>). As you may have noticed, the default error message does not do a good job describing what went wrong, so it's probably a good idea for your template to specifically address that case, even if addressing that case just means providing a more understandable compiler error.

JaMiT
  • 14,422
  • 4
  • 15
  • 31
1

From what I understand, the ternary operator needs to evaluate its two operands, so it can not be used in this type of recursion.

Not evaluation, it's instantiation. When the expression std::is_same_v<Searching, First> ? 0 : 1 + index_of<Searching, Others...> is instantiated, all the three operands must be instantiated (not evaluated), so an error occurs due to the instantiation of index_of<Searching, Others...>. It is analogous to the difference between if and if constexpr. If you change the if constexpr in your second way to if, it does not compile too.

A workaround is to use your second way (i.e. function template) to initialize index_of, like

template<typename Searching, typename First, typename... Others>
constexpr std::size_t IndexOf() {
    if constexpr(std::is_same_v<Searching,First>)
        return 0;
    else return 1 + IndexOf<Searching,Others...>();
};

template<typename Searching, typename First, typename...Others>
constexpr std::size_t index_of = IndexOf<Searching, First, Others...>();

or use template specialization:

template<typename Searching, typename First, typename... Others>
constexpr std::size_t index_of = 1 + index_of<Searching, Others...>;

template<typename Searching, typename... Others>
constexpr std::size_t index_of<Searching, Searching, Others...> = 0;

If you want a clearer error message, you can wrap the variable template in a class and use static_assert.

xskxzr
  • 12,442
  • 12
  • 37
  • 77