【问题标题】:What is the correct syntax for concept in combination with a templated struct or class?结合模板结构或类的概念的正确语法是什么?
【发布时间】:2020-03-08 18:18:13
【问题描述】:

最近我使用concepts 为templated 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;
}

You can run the above code online。 我想知道为什么 Foo 和 Bar 编译得很好,而 Baz 和 Qux 如果未注释则不会编译。 恕我直言,Baz 和 Qux 的语法更方便。

【问题讨论】:

  • Foo(3.14159)Bar(true) 都不应该编译...
  • @Barry:对不起,我不能跟...为什么不应该 Foo(3.14159)Bar(true) 编译?使用编译器 Clang(主干)函数 Foo(3.14159)Bar(true) 进行编译!
  • 是的,这是一个 Clang 错误。
  • @Barry:感谢您的回答!注意只使用 gcc (trunk) Qux(true)Qux(3.14159) 编译。我们应该做什么?报告 Clang 错误?
  • 我回家后会提交一份错误报告。

标签: c++ c++20 c++-concepts


【解决方案1】:

让我们按顺序浏览所有类模板。我将使用一个更简单的概念,因为 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);

在进行类模板参数推导时,过程是我们获取所有构造函数并将它们转换为函数 - 然后执行重载决策以找出我们最终得到的特定类型。对于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&lt;bool&gt;)但__f(3.14159) 格式不正确 - 第一个重载不可行,因为double 不是bool,第二个重载不是可行,因为没有推断出T

至少这是规则应该。今天存在的措辞缺少我们将约束从每个构造函数转移到重载集的规则,而 clang 恰好遵循这里规则的字母 - 它的 __f 版本没有任何 @ 987654336@ 附在他们身上。但这绝对不是我们想要在这里发生的事情,而且肯定会成为核心问题。另见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&lt;double&gt;

对于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>);
};

这里,两个构造函数都参与了 CTAD,所以Qux(true)Qux(3.14159) 都可以正常工作(只是每个都选择不同的构造函数)。这和我们之前看到的行为是一样的——clang 遵循它们本来的规则,而 gcc(和 msvc)遵循它们应该遵循的规则。

【讨论】:

    猜你喜欢
    • 2014-05-05
    • 1970-01-01
    • 2018-08-12
    • 1970-01-01
    • 1970-01-01
    • 2015-02-07
    • 1970-01-01
    • 1970-01-01
    • 2022-01-19
    相关资源
    最近更新 更多