Не удалось преобразовать список инициализаторов, заключенных в фигурные скобки, в стандартный кортеж.

В рамках более крупного проекта я играю с std::tuple и шаблонами; рассмотрим следующий код:

template <typename ...T> void foo(tuple<T...> t) {}
void bar(tuple<int, char> t) {}
tuple<int, char> quxx() { return {1, 'S'}; }

int main(int argc, char const *argv[])
{
    foo({1, 'S'});           // error
    foo(make_tuple(1, 'S')); // ok
    bar({1, 'S'});           // ok
    quxx();                  // ok
    return 0;
}

Согласно этот ответ C++17 поддерживает инициализацию кортежа из copy-list-initialization, однако кажется такая поддержка ограничена, так как я получаю следующую ошибку (GCC 7.2.0):

main.cpp: In function 'int main(int, const char**)':
main.cpp:14:17: error: could not convert '{1, 'S'}' from '<brace-enclosed initializer list>' to 'std::tuple<>'
     foo({1, 'S'}); // error
                 ^

Можно ли в этом сценарии использовать синтаксис, заключенный в фигурные скобки?

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

Дополнительно: Clang 6 также жалуется

prog.cc:12:5: error: no matching function for call to 'foo'
    foo({1, 'S'});           // error
    ^~~
prog.cc:6:31: note: candidate function [with T = <>] not viable: cannot convert initializer list argument to 'tuple<>'
template <typename ...T> void foo(tuple<T...> t) {}

person Samuele Pilleri    schedule 25.04.2018    source источник


Ответы (2)


список инициализации в фигурных скобках, такой как {1, 'S'}, на самом деле не имеет типа. В контексте шаблонного вывода вы можете использовать их только в определенных случаях — при выводе против initializer_list<T> (где T — параметр шаблона функции) или когда соответствующий параметр уже выведен чем-то другим. В этом случае ни одна из этих двух вещей не верна, поэтому компилятор не может понять, что должно быть ...T.

Таким образом, вы можете указать типы напрямую:

foo<int, char>({1, 'S'});

Или вы можете создать tuple самостоятельно и передать это:

foo(std::tuple<int, char>(1, 'S')); // most explicit
foo(std::tuple(1, 'S')); // via class template argument deduction

Сегодня ClassTemplate<Ts...> можно вывести только из выражений типа ClassTemplate<Us...> или типов, унаследованных от чего-то подобного. Гипотетическое предложение может расширить это, чтобы дополнительно попытаться выполнить вывод аргумента шаблона класса для выражения, чтобы увидеть, будет ли этот вывод успешным. В этом случае {1, 'S'} не является tuple<Ts...>, но tuple __var{1, 'S'} успешно выводит tuple<int, char>, так что это сработает. Такое предложение также должно было бы решать такие вопросы, как... что, если мы выводим ClassTemplate<T, Ts...> или любую незначительную вариацию, что не является чем-то, что позволяет дедукция аргумента шаблона класса (но это то, что многие люди время от времени выражали заинтересованность в том, чтобы быть способен сделать).

Я не знаю о таком предложении сегодня.

person Barry    schedule 25.04.2018
comment
Можем ли мы предположить, что это изменится? Есть ли какие-либо предложения по этому поводу, может быть, для С++ 2a? - person Samuele Pilleri; 26.04.2018
comment
@SamuelePilleri Нет, не будет. - person Barry; 26.04.2018

В соответствии с этим ответом С++ 17 поддерживает инициализацию кортежа из инициализации списка копирования, однако кажется, что такая поддержка ограничена, поскольку я получаю следующую ошибку

Проблема в другом.

Когда вы вызываете bar({1, 'S'}), компилятор знает, что bar() получает tuple<int, char>, поэтому принимает 1 как int, а 'S' как char.

См. другой пример: если вы определяете

void baz (std::tuple<int> const &)
 { }

ты можешь позвонить

baz(1);

потому что компилятор знает, что baz() получит std::tuple<int>, поэтому возьмите 1 для инициализации int в кортеже.

Но с

template <typename ...T>
void foo(tuple<T...> t)
 { }

компилятор не знает T... типов; когда ты звонишь

foo({1, 'S'}); 

какие T... типы должен выводить компилятор?

Я вижу, по крайней мере, две гипотезы: T = int, char или T = std::pair<int, char>; или также T = std::tuple<int, char>.

Какая гипотеза должна следовать за компилятором?

Я имею в виду: если вы передаете std::tuple в foo(), компилятор принимает список типов в кортеже как список T...; но если вы передаете что-то еще, компилятор должен вывести правильный std::tuple; но этот вывод в данном случае не единственный. Итак, ошибка.

person max66    schedule 25.04.2018
comment
Вот что меня обмануло! Я имею в виду, что foo ожидает std::tuple, это должно быть довольно просто. - person Samuele Pilleri; 26.04.2018
comment
@SamuelePilleri - И если вы передадите std::tuple, это сработает; но если вы передаете что-то еще, компилятор должен вывести правильный кортеж; но этот вывод в данном случае не единственный. Итак, ошибка. - person max66; 26.04.2018
comment
Разве он не может использовать те же правила, что и вывод аргумента шаблона класса? - person Samuele Pilleri; 26.04.2018
comment
@SamuelePilleri - я не знаю; Я полагаю, будут некоторые проблемы; так или иначе, из std::tuple t({1, 'S'}) какой std::tuple следует вывести? std::tuple<std::pair<int, char>>? Или std::tuple<std::tuple<int, char>>? Остается проблема в том, что из {1, 'S'} нельзя вывести уникальный тип. - person max66; 26.04.2018
comment
@ max66 Предположительно, если бы у нас было это правило, мы бы не вставляли круглые скобки произвольно? - person Barry; 26.04.2018
comment
@ max66 Полностью согласен, но поскольку и tuple x(1, 'S'), и tuple x{1, 'S'} работают, я подумал, почему бы и нет? В любом случае, спасибо вам обоим за разъяснения. - person Samuele Pilleri; 26.04.2018
comment
@Barry - вы имеете в виду, что что-то вроде foo({1, {'S', 2L}}) может добавить рекурсивную двусмысленность? - person max66; 26.04.2018
comment
@ max66 Я не знаю, почему двусмысленность - единственный способ, который мог бы сработать, - это если бы мы просто решили, что списки инициализации в фигурных скобках были tuples. - person Barry; 26.04.2018
comment
@ Барри, значит, из {1, {'S', 2L}} мы можем вывести std::tuple<int, std::tuple<char, long>>? Во всяком случае, я полагаю, что это возможно: учитывая, что целью является std::tuple<Ts...>, инициализируя с помощью {as...}, мы могли бы дать предпочтительный вывод для std::tuple<decltype(as)...>; и если мы хотим std::tuple<std::pair<int, char>>, мы можем указать foo<std::pair<int, char>>({1, 'S'}) или foo(std::make_pair(1, 'S')). Но нет ли риска, что при введении этого правила какой-то старый код изменит поведение? - person max66; 26.04.2018
comment
Не понимаю, почему вы продолжаете упоминать pair, здесь это не уместно. Хотя может быть разумным разрешить вычитание tuple<T...> из {1,'S'}, у вас по крайней мере есть tuple, чтобы управлять своим вычетом. Как бы вы определили, что внутренний список инициализации в фигурных скобках должен также быть tuple? Вам вообще не на что ориентироваться. Я не понимаю, насколько разумно просто предположить, что мы повторяем шаблон внешнего класса. - person Barry; 26.04.2018