Почему рекурсивное значение шаблона constexpr не компилируется?

Я определяю способ узнать положение типа в списке типов, используя рекурсивные шаблоны в C++17. Я попробовал два способа: один с использованием значения constexpr и один с использованием функции constexpr. Второе, используя if оператор, компилируется, в то время как первый, используя тернарный оператор, не компилируется.

#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;
}

Мой компилятор, migw64, говорит:

неверное количество аргументов шаблона (1, должно быть не менее 2)
constexpr std:: size_t index_of = std:: is_same_v<Поиск, первый>? 0: 1 + index_of<Поиск, другие...>;

Из того, что я понимаю, троичный оператор должен оценивать два своих операнда, поэтому его нельзя использовать в рекурсии этого типа.

Я прав? и если да, то почему это так?
Спасибо.

2 ответа

Решение

Я собираюсь начать в конце вопроса, а затем работать.

Из того, что я понимаю, троичный оператор должен оценить свои два операнда

Нет. Тернарный (что означает "сделан из трех") оператор имеет три операнда, а не два. При оценке этого оператора оцениваются два из трех операндов: условие и какой операнд выбирает условие.

Оценка не там, где твоя проблема.

первый, использующий троичный оператор, не компилируется.

Я думаю, я понимаю, почему это так. Вы присваиваете результат условного оператора std::size_t, Чтобы это скомпилировать, тип этого результата должен быть std::size_t или конвертируемые в этот тип. Таким образом, компилятор должен определить тип результата. Я нашел правила для определения типа. Первое правило применяется, если у второго или третьего операнда есть тип void, Таким образом, даже если один из этих операндов не будет оценен, оба их типа должны быть известны.

Итак, какой тип вашего третьего операнда, который не будет оцениваться? Ну, это же 1 + index_of<int>так что нам лучше проверить декларацию index_of<int>, Нам нужны два параметра. Cue сообщение об ошибке.

Это, вероятно, то, с чем вам придется столкнуться в любом случае, так как вы должны получить одинаковую ошибку для любого подхода в случае "не найден" (например: index_of<unsigned, int, long, float>). Как вы, возможно, заметили, сообщение об ошибке по умолчанию не дает хороших результатов, описывая, что пошло не так, поэтому, вероятно, хорошей идеей для вашего шаблона является конкретное рассмотрение этого случая, даже если обращение к этому случаю просто означает предоставление более понятной ошибки компилятора.

Из того, что я понимаю, троичный оператор должен оценивать два своих операнда, поэтому его нельзя использовать в рекурсии этого типа.

Не оценка, это инстанцирование. Когда выражение std::is_same_v<Searching, First> ? 0 : 1 + index_of<Searching, Others...> создается экземпляр, все три операнда должны быть созданы (не оценены), поэтому возникает ошибка из-за создания экземпляра index_of<Searching, Others...>, Это аналогично разнице между if а также if constexpr, Если вы измените if constexpr вторым способом ifон тоже не компилируется.

Обходной путь - использовать ваш второй способ (т.е. шаблон функции) для инициализации index_of, лайк

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...>();

или используйте специализацию шаблона:

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;

Если вы хотите более четкое сообщение об ошибке, вы можете заключить шаблон переменной в класс и использовать static_assert,

Другие вопросы по тегам