C введите признаки if_v (автоматическое выведение типа обеспечение того же типа)

#c #if-statement #c 17 #typetraits #variable-templates

#c #if-statement #c 17 #typetraits #переменная-шаблоны

Вопрос:

Рассмотрим следующий код

 #include <type_traits>

template<bool Test, class T, T val1, T val2>
constexpr T if_v = std::conditional_t<Test, 
                                      std::integral_constant<T, val1>, 
                                      std::integral_constant<T, val2>>::value;

int main()
{
    constexpr size_t value1 = 123;
    constexpr size_t value2 = 456;
    constexpr bool test     = (3 > 2);

    constexpr size_t r0 = if_v<test, size_t, value1, value2>;  // = 123

    return 0;
}
 

Поскольку во время компиляции мы знаем, что такое типы value1 и value2 , нам не нужно указывать это. Итак, мы могли бы написать

 template<bool Test, auto val1, auto val2>
constexpr decltype(val1) if_v = std::conditional_t<Test, 
                                                   std::integral_constant<decltype(val1), val1>, 
                                                   std::integral_constant<decltype(val2), val2>>::value;
 

чтобы мы могли написать упрощенный оператор if if_v<test, value1, value2> (без типа). В идеале я также хотел бы убедиться, что оба входных значения имеют один и тот же тип. Но я не уверен, как добиться этого при использовании auto .


В принципе, есть ли лучшие способы определить if_v так, чтобы мы могли писать if_v<test, value1, value2> без необходимости указывать тип, а также каким static_assert -то образом изменять равенство типов?

Комментарии:

1. Одним из решений является создание резервного класса для if_v, который выполняет статическое утверждение в своем теле. Я попробовал это, и это сработало. Я воздержусь от публикации этого в качестве ответа, поскольку подозреваю, что есть более простые или более элегантные решения (если это так, я полагаю, что кто-то еще их опубликует).

2. Разве вы не можете просто использовать ? : ? Чего мне не хватает?

Ответ №1:

Я также хотел бы убедиться, что оба входных значения имеют один и тот же тип. Но я не уверен, как добиться этого при использовании auto.

Как насчет использования SFINAE?

Я имею в виду

 template <bool Test, auto v1, auto v2,
          std::enable_if_t<std::is_same_v<decltype(v1), decltype(v2)>, int> = 0>
constexpr auto if_v = std::conditional_t<Test, 
                           std::integral_constant<decltype(v1), v1>, 
                           std::integral_constant<decltype(v2), v2>>::value;
 

или, может быть, просто

 template <bool Test, auto v1, auto v2,
          std::enable_if_t<std::is_same_v<decltype(v1), decltype(v2)>, int> = 0>
constexpr auto if_v = Test ? v1 : v2;
 

Комментарии:

1. О, очень приятно 🙂 Ограничение шаблона здесь намного лучше, чем косвенное использование.

2. Второе простое решение — это то, что я искал, большое спасибо!

Ответ №2:

Как это часто бывает, вы можете решить эту проблему, добавив еще один уровень косвенности. Создайте свою первую версию if_v , которая явно использует тип в деталях реализации:

 template<bool Test, class T, T val1, T val2>
constexpr T if_v_impl = std::conditional_t<Test,
                            std::integral_constant<T, val1>, 
                            std::integral_constant<T, val2>>::value;
 

Теперь вы можете реализовать версию с выведенными типами заполнителей, проверив, совпадают ли выведенные типы, и вызывая только if_v_impl в этом случае:

 template<bool Test, auto val1, auto val2>
constexpr decltype(val1) if_v = std::is_same_v<decltype(val1), decltype(val2)> 
                                ? if_v_impl<Test, decltype(val1), val1, val2> 
                                : throw; 
 

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

Вот демонстрация.