Шаблон Variadic компилируется только при объявлении пересылки - PullRequest
17 голосов
/ 21 июня 2019

У меня есть шаблон variadic, который наследуется от всех аргументов шаблона:

template <typename... Ts>
struct derived : Ts...
{
};

Я также хотел бы иметь возможность для выражения типа "существующий derived с добавленными аргументами шаблона". Моя попытка это:

// Do not ODR-use (goes in namespace impl or similar)!
template<class ... NewInputs, class ... ExistingInputs>
auto addedHelper(const derived<ExistingInputs...>&)
    -> derived<ExistingInputs..., NewInputs...>;

template<class ExistingInput, class ... NewInputs>
using Added = decltype(addedHelper<NewInputs...>(std::declval<ExistingInput>()));

В качестве простого примера, Added<derived<A, B>, C> должно быть derived<A, B, C>. Я использую вспомогательную функцию для вывода аргументов шаблона из первого пакета параметров.

Моя проблема: по какой-то причине я могу успешно использовать это с неполными типами, если derived был объявлен форвардом, но не , если он был определен.

Почему этот код не компилируется :

#include <utility>

template <typename... Ts>
struct derived : Ts...
{};

template<class ... NewInputs, class ... ExistingInputs>
auto addedHelper(const derived<ExistingInputs...>&)
    -> derived<ExistingInputs..., NewInputs...>;

template<class ExistingInput, class ... NewInputs>
using Added = decltype(addedHelper<NewInputs...>(std::declval<ExistingInput>()));


struct A;
struct B;
struct C;

// Goal: This forward declaration should work (with incomplete A, B, C).
auto test(derived<A, B> in) -> Added<decltype(in), C>;


struct A {};
struct B {};
struct C {};

void foo()
{
    auto abc = test({});

    static_assert(std::is_same_v<decltype(abc), derived<A, B, C>>, "Pass");
}

Принимая во внимание, что этот код компилируется :

#include <utility>

template <typename... Ts>
struct derived;


template<class ... NewInputs, class ... ExistingInputs>
auto addedHelper(const derived<ExistingInputs...>&)
    -> derived<ExistingInputs..., NewInputs...>;

template<class ExistingInput, class ... NewInputs>
using Added = decltype(addedHelper<NewInputs...>(std::declval<ExistingInput>()));


struct A;
struct B;
struct C;

// Goal: This forward declaration should work (with incomplete A, B, C).
auto test(derived<A, B> in) -> Added<decltype(in), C>;


template <typename... Ts>
struct derived : Ts...
{};


struct A {};
struct B {};
struct C {};

void foo()
{
    auto abc = test({});

    static_assert(std::is_same_v<decltype(abc), derived<A, B, C>>, "Pass");
}

Для удобства, здесь оба случая одновременно (комментарий в / из #define FORWARD_DECLARED): https://godbolt.org/z/7gM52j

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

1 Ответ

8 голосов
/ 21 июня 2019

Наблюдение Эвга бьет ногтем по голове: проблема здесь в ADL. Это на самом деле та же проблема, с которой я столкнулся с этим вопросом .

Проблема заключается в следующем: у нас здесь неквалифицированный звонок:

template<class ExistingInput, class ... NewInputs>
using Added = decltype(addedHelper<NewInputs...>(std::declval<ExistingInput>()));
//                     ^^^^^^^^^^^

Мы знаем, что это шаблон функции, потому что мы находим его в обычном поиске, поэтому нам не нужно разбираться со всем вопросом «является ли < оператор или вводчик шаблона». Однако, поскольку это неквалифицированный вызов, мы также должны выполнить поиск, зависящий от аргумента.

ADL нужно изучить связанные пространства имен всех аргументов, что выглядит нормально - нам не нужны полные типы для этого. Но ADL также должен искать потенциальные функции-друзья и шаблоны функций, определенные в классах. Ведь для этого нужно работать:

struct X {
    friend void foo(X) { }
};
foo(X{}); // must work, call the hidden friend defined within X

В результате в нашем звонке идет речь:

auto test(derived<A, B> in) -> Added<decltype(in), C>;

Мы должны создать экземпляр derived<A, B> ... но этот тип наследуется от двух неполных классов, что мы не можем сделать. Вот где проблема, вот где мы терпим неудачу.

Вот почему работает версия предварительной декларации. template <typename... T> struct derived; неполно, поэтому просто попытаться заглянуть внутрь него для поиска функций-друзей, тривиально ничего не находит - нам не нужно создавать что-либо еще.

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

<ч />

К счастью, это можно исправить в связи с тем, что предложил Эвг. Сделайте квалифицированный звонок:

template<class ExistingInput, class ... NewInputs>
using Added = decltype(::addedHelper<NewInputs...>(std::declval<ExistingInput>()));

Это позволяет избежать ADL, который вы даже не хотели. В лучшем случае вы избегаете делать то, что не приносит вам никакой пользы. Плохой случай, ваш код не компилируется. Злой случай, для некоторых входов вы случайно вызываете другую функцию полностью.

<Ч />

Или просто используйте Boost.Mp11 mp_push_back

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...