std :: pair <auto, auto> тип возвращаемого значения

16

Я играл с autoв std::pair. В приведенном ниже коде функция fдолжна возвращать std::pairтипы, которые зависят от параметра шаблона.

Рабочий пример:

Пример 1

template <unsigned S>
auto f()
{
    if constexpr (S == 1)
        return std::pair{1, 2}; // pair of ints
    else if constexpr (S == 2)
        return std::pair{1.0, 2.0}; // pair of doubles
    else
        return std::pair{0.0f, 0.0f}; // pair of floats
}

Это работает с gcc 9.2, gcc 10.0, clang 9.0 и clang 10.0.

Далее я хотел явно написать тип возвращаемого значения в std::pairцелях ясности:

Пример 2

template <unsigned S>
std::pair<auto, auto> f()
{
    if constexpr (S == 1)
        return {1, 2};
    /* ... */
}

И gcc 9.2 / 10.0 и clang 9.0 / 10.0 не смогли скомпилировать это.

GCC 9,2

error: invalid use of 'auto'
error: template argument 1 is invalid // first argument (auto) of std::pair
error: template argument 2 is invalid // second argument (auto) of std::pair
error: cannot convert '<brace-enclosed initializer list>' to 'int' in return

Судя по последнему сообщению об ошибке, gcc 9.2 считает, что std::pair<auto, auto>это int. Как это можно объяснить?

gcc 10.0

error: returning initializer list

Эта ошибка понятна, однако я ожидал, что std::pairбудет вызван конструктор, или я что-то здесь упускаю?

лязг 9,0 и 10,0

'auto' not allowed in template argument
excess elements in scalar initializer
no matching function for call to 'f'

Хорошо, Clang не нравится ничего из этого. Из второго сообщения об ошибке кажется, что Clang также считает, что тип возвращаемого значения int.

Наконец, чтобы исправить ошибку, полученную при компиляции с gcc 10.0, я решил вернуть std::pairявно:

Пример 3

template <unsigned S>
std::pair<auto, auto> f()
{
    if constexpr (S == 1)
        return std::pair{1, 2};
    /* ... */
}

лязг 9,0 и 10,0

То же, что и раньше, но с дополнительным:

no viable conversion from returned value of type 'std::pair<int, int>' to function return type 'int'

Здесь Clang все еще думает, что мы возвращаемся int?

GCC 9,2

Так же, как и раньше.

gcc 10.0

Оно работает!

Я предполагаю, что некоторые функции все еще должны быть реализованы, или в одной из ситуаций, описанных выше, есть ли компилятор, который является правильным, а другой неправильным? На мой взгляд, пример 2 должен работать. Или нет?

mfnx
источник

Ответы:

23

Синтаксис:

std::pair<auto, auto> f() { return std::pair(1, 2); }
~~~~~~~~~~~~~~~~~~~~~

Был частью оригинальной концепции Concepts TS, но не был включен в предложение концепции, которая является частью C ++ 20. По существу, единственными типами заполнителей в C ++ 20 являются auto(и их варианты, подобные auto**) decltype(auto)и ограниченные заполнители ( Concept autoи их варианты). Этот тип вложенного типа заполнителя был бы очень полезен, но не является частью C ++ 20, так что объявление функции некорректно.

Теперь gcc позволяет это, потому что gcc реализовал Concepts TS, и я думаю, что они решили сохранить эту функцию. Clang никогда не реализовывал TS, так что нет.

В любом случае, это:

std::pair<auto, auto> f() { return {1, 2}; }

Всегда будет плохо сформирован. Смысл синтаксиса в том, что мы выводим тип возвращаемого значения, а затем требуем, чтобы он совпадал pair<T, U>для некоторых типов Tи U. Мы в основном пытаемся вызвать изобретенную функцию:

template <typename T, typename U>
void __f(std::pair<T, U>);

__f({1, 2}); // this must succeed

Но вы не можете вывести тип из {1, 2}- у braced-init-list нет типа. Возможно, это то, что следует изучить (как легко понять, по крайней мере, в таком простом случае, как этот), но это никогда не было разрешено. Так что отвергать это правильно в любом случае.

И, наконец:

GCC 9.2, кажется, считает, что std::pair<auto, auto>это int. Как это можно объяснить?

По какой-то причине (возможно, из-за нашего наследства C с неявным int), когда gcc не распознает или не понимает тип, он просто использует intв качестве заполнителя в сообщениях об ошибках. Это очень запутанно, потому что очевидно, что это gcc, intа не исходный код. Но так оно и есть.

Барри
источник
«Braced-init-list не имеет аргумента типа» мне не понятен. std :: pair <int, int> f () {return {1,2}; } работает, и {1,2} не имеет типа (он вызывает конструктор std :: pair <int, int>, как я понимаю). Может быть, с помощью <auto, auto> компилятор не может определить типы 1 и 2 в списке инициализаторов {1, 2}?
mfnx
@mfnx Not не имеет аргумента типа , просто не имеет типа. Списки инициализации в фигурных скобках можно использовать только в определенных ситуациях, например при инициализации известного типа. Но они не могут быть использованы в дедукции - потому что они не имеют типа. За исключением auto x = {1, 2};работ, но только если все типы одинаковы.
Барри
2
Большинство компиляторов вместо того, чтобы останавливаться на первой ошибке, пытаются восстановить ее, чтобы они могли сообщать о дополнительных ошибках. Как правило, это означает, что все неразборчиво является int. Это не то, что intзаполнитель в сообщениях об ошибках; компилятор действительно думает, что это int. (Чтобы сделать это более понятным, gcc, вероятно, должен был в какой-то момент сказать «предполагая int».)
Раймонд Чен
2
Обратите внимание, что другой возможный путь расширения - разрешить вычет аргументов шаблона класса для возвращаемых типов, поскольку это std::pair __f{1,2};работает.
Дэвис Херринг
2
@DavisHerring Я бы не хотел std::optional f() { return 4; }работать.
Барри