Каков правильный синтаксис для концепции в сочетании с шаблонной структурой или классом? - PullRequest
1 голос
/ 08 марта 2020

Недавно я использовал concept s для определения различных конструкторов для template d struct. Вот код:

#include <iostream>

namespace detail {
    template<typename T, typename U >
    concept SameHelper = std::is_same_v<T, U>;
}

template<typename T, typename U >
concept same_as = detail::SameHelper<T, U> && detail::SameHelper<U, T>;

template<typename T>
concept trivial = same_as<T, bool> || same_as<T, char>;

template<typename T>
concept not_trivial = !trivial<T>;

template<typename T>
struct Foo
{
    Foo(T t) requires trivial<T> : member{t} { std::cout << "Foo is trivial" <<std::endl; }
    Foo(const auto& t) requires not_trivial<T> : member{t} { std::cout << "Foo is not trivial" <<std::endl;}
    const T member;
};

template<typename T>
struct Bar
{
    Bar(auto t) requires trivial<T> : member{t} { std::cout << "Bar is trivial" <<std::endl; }
    Bar(const T& t) requires not_trivial<T> : member{t} { std::cout << "Bar is not trivial" <<std::endl;}
    const T member;
};

template<typename T>
struct Baz
{
    Baz(auto t) requires trivial<T> : member{t} { std::cout << "Baz is trivial" <<std::endl; }
    Baz(const auto& t) requires not_trivial<T> : member{t} { std::cout << "Baz is not trivial" <<std::endl;}
    const T member;
};

template<typename T>
struct Qux
{
    Qux(T t) requires trivial<T> : member{t} { std::cout << "Qux is trivial" <<std::endl; }
    Qux(const T& t) requires not_trivial<T> : member{t} { std::cout << "Qux is not trivial" <<std::endl;}
    const T member;
};

int main()
{
    Foo(true);
    Foo(3.14159);

    Bar(true);
    Bar(3.14159);

    //Baz(true);    // does not compile if uncommented
    //Baz(3.14159); // does not compile if uncommented

    //Qux(true);    // does not compile if uncommented
    //Qux(3.14159); // does not compile if uncommented

    return 0;
}

Вы можете запустить вышеуказанный код онлайн . Мне интересно, почему Foo и Bar компилируются нормально, тогда как Baz и Qux не компилируются, если не комментированы. ИМХО синтаксис для Baz и Qux намного удобнее.

1 Ответ

2 голосов
/ 08 марта 2020

Давайте go через все шаблоны классов по порядку. Я собираюсь использовать более простую концепцию, поскольку bool является единственным релевантным типом:

template <typename T>
struct Foo
{
    Foo(T) requires same_as<T, bool>;
    Foo(const auto&) requires (not same_as<T, bool>);
};

Foo(true);
Foo(3.14159);

При выполнении вывода аргументов шаблона класса мы получаем все конструкторы и превращаем их в функции - и затем выполните разрешение перегрузки, чтобы выяснить, к какому конкретному типу c мы пришли. Для Foo это будет:

template <typename T> requires same_as<T, bool>
auto __f(T) -> Foo<T>;

template <typename T> requires (not same_as<T, bool>)
auto __f(const auto&) -> Foo<T>;

__f(true);    // from Foo(true)
__f(3.14159); // from Foo(3.14159)

При первой перегрузке __f, T выводится из аргумента. Во второй перегрузке это не так - нет никакого способа определить, что такое T ... так что это в принципе не имеет значения, насколько идет процесс CTAD. В результате, __f(true) в порядке (вы получаете обратно Foo<bool>), но __f(3.14159) плохо сформирован - первая перегрузка не жизнеспособна, поскольку double не bool, а вторая перегрузка - нет жизнеспособный, потому что T не выводится.

По крайней мере, таковы должны быть правила . В существующей сегодня формулировке отсутствует правило, согласно которому мы переносим ограничения из каждого конструктора в набор перегрузки, и случается, что clang следует букве правил здесь - его версия __f не имеет requires прилагается к ним. Но это определенно не то, что мы хотим, чтобы произошло здесь, и, безусловно, будет основной проблемой. См. Также llvm bug # 44484 .

Bar аналогичен, только с переброшенными аргументами:

template<typename T>
struct Bar
{
    Bar(auto) requires same_as<T, bool>;
    Bar(const T&) requires (not same_as<T, bool>);
};

Здесь единственный конструктор, который может дать нам Ответ для CTAD - второй, но второй требует, чтобы T не было bool. Так что Bar(true) плохо сформирован, но Bar(3.14159) в порядке и дает вам Bar<double>.

Для Baz:

template<typename T>
struct Baz
{
    Baz(auto) requires same_as<T, bool>;
    Baz(const auto&) requires (not same_as<T, bool>);
};

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

И Qux:

template<typename T>
struct Qux
{
    Qux(T) requires same_as<T, bool>;
    Qux(const T&) requires (not same_as<T, bool>);
};

Здесь оба конструктора do участвуют в CTAD, поэтому Qux(true) и Qux(3.14159) работают нормально (только каждый выбирает другой конструктор). Это такое же поведение, как мы видели раньше - clang следует правилам, как они, а g cc (и msv c) следуют правилам, которыми они должны быть.

...