Является ли возврат declval UB, если шаблон никогда не вызывается вне не оцененного контекста?

#c #c 17 #metaprogramming

Вопрос:

Я пишу некоторые метафункции, и у меня возникла идея написать их с использованием C 17, если constexpr в сочетании с выведенным типом возвращаемого значения. Вот пример :

 #include <tuple>
#include <type_traits>
#include <string>

template<typename TupleType, std::size_t I = 0, typename... ReturnData>
constexpr auto filterImpl()
{
    if constexpr (I < std::tuple_size_v<TupleType>)
    {
        using Type = std::tuple_element_t<I, TupleType>;

        if constexpr (std::is_arithmetic_v<Type>)
            return filterImpl<TupleType, I   1, ReturnData..., Type>();
        else
            return filterImpl<TupleType, I   1, ReturnData...>();
    }
    else
        return std::declval<std::tuple<ReturnData...>>();
}
template<typename Tuple>
using FilterTuple = decltype(filterImpl<Tuple>());

//std::tuple<int, float>
using MyTuple = FilterTuple<std::tuple<int, std::string, float, int*>>;

int main(int argc, char* argv[])
{
    MyTuple t{ 3, 5.f };

    return 0;
}
 

Конечно, вы, вероятно, могли бы использовать более простой шаблон для решения этой проблемы, но это всего лишь основной пример того, что я хочу сделать.

Этот код компилируется в msvc, но не в onlinegdb. Какой из них правильный? Разрешено ли использовать declval таким образом? Должен ли я вообще писать свои метафункции таким образом, или мне следует вернуться к классам шаблонов?

(Я думаю, что, если есть способ сделать это, более сложные метафункции могли бы быть легче прочитаны таким образом)

А Маквей:

 #include <utility>

template<bool b>
constexpr auto evil()
{
    if constexpr (b)
        return 0;
    else
        return std::declval<int>();
}

template<bool b>
using Test = decltype(evil<b>());

int main()
{
    Test<false> t{};
}
 

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

1. допустимо ли иметь несогласованные типы возврата для типа auto возврата? потому что, похоже, вы это делаете, и я подумал, что это неверно.

2. @Afshin это только те ветви constexpr if , которые отбрасываются

3. @Afshin: if constexpr не отбрасывает операторы возврата, которые просто недоступны из-за предыдущего оператора возврата, но он удалит их по else мере необходимости.

4. @Afshin подумайте о том, чтобы опубликовать это как вопрос. Код в комментариях чрезвычайно трудно читать. Я смотрел на него с минуту, чтобы понять, что на самом деле они разные. И в любом случае, комментарии не для вопросов и ответов, вот для чего нужны вопросы и ответы 😉

5. Это зависит от того, используется ли std::declval здесь odr. Что, в свою очередь, зависит от того, является ли это потенциально оцениваемым выражением. Я думаю, что это так: «Выражение потенциально оценивается,если только оно не является недооцененным операндом (пункт 8) или его подвыражением». Тело функции не является подвыражением выражения вызова функции. Поэтому я думаю, что на самом std::declval<std::tuple<ReturnData...>>() деле odr используется, когда шаблон функции создается таким образом, чтобы достичь этой ветви.

Ответ №1:

Поскольку вы должны создать экземпляр шаблона функции, чтобы определить ее тип возвращаемого значения, существует функция, которая использует odr std::declval<…> , поэтому программа плохо сформирована. MSVC ошибочно принимает это (без предупреждения), хотя можно утверждать, что это правило не должно требовать диагностики, чтобы разрешить очевидную стратегию реализации, заключающуюся в том, чтобы просто не дать определения std::declval .