C++ 14中的模拟模板约束

发布于 2025-01-21 12:41:05 字数 3677 浏览 3 评论 0 原文

我在C ++中编码14一个使用模板类和功能的程序。当模板类型无法满足我对STD :: ENABLE的要求时,我不喜欢我收到的编译器错误消息,

如果我想知道我是否可以更好地尝试模仿C ++ 20概念的某些功能。以下是我必须定义的一些想法,该“概念”检查了某些模板参数的任意数量表达式的有效性

  • ,我想检查t and u是否支持加法操作员,我会使用一个测试类似的模板别名
    template <typename T, typename U = T>
    using addition_test = decltype(declval<T>() + declval<U>());
  • ,然后我将定义一个简单的要求
    template <typename ...T> 
    using addition_req = is_detected<addition_test, T...> 
template <typename T, typename U = T>
using addition_group_concept = conjunction <
        addition_req<T, U>,
        subtraction_req<T, U>,
        unary_plus_req<T>,
        unary_minus_req<T>
>;
  • 最后,我可以使用我的“概念”,
template <typename T, typename U>
auto add (T const & t, U const & u) ->
std::enable_if_t<addition_group_concept<T, U>::value, decltype(t + u)>
{
    return t + u;
}

template <typename T, typename U>
auto add (T const & t, U const & u) ->
std::enable_if_t<!addition_group_concept<T, U>::value, void>
{
    static_assert(always_false<T>::value,
                                "The operands must support addition and substraction operations.");
}

如果不满足我的概念,我会收到一条编译器错误消息,它比您通常的失败模板替换消息短一些,较少的隐态。到目前为止还不错(?)...但是

我以为我认为我可以用尾随的返回类型编写虚拟函数,然后将我的要求序列放在 std :: enable_if 中在尾声返回类型中。然后,我可以在模板别名的帮助下定义我的概念,该模板别名被用作适配器。像这样

// void_t is the c++17 void_t defined as:
// template <typename ...T>
// using void_t = void;

    template <typename ...Requirements>
    using requirement_seq = std::enable_if_t<conjunction<Requirements...>::value>;

    template <typename T>
    using simple_req = std::is_same<void_t<T>, void>;

    template <typename T, typename U = T>
    auto addition_group (T t, U u) -> requirement_seq<
        simple_req<decltype(t + u)>,
        simple_req<decltype(t - u)>,
        simple_req<decltype(+t)>,
        simple_req<decltype(-t)>,
        simple_req<decltype(+u)>,
        simple_req<decltype(-u)>
        >;

    template <typename ...T>
    using addition_group_adapter = decltype(addition_group<T...>);

    template <typename T, typename U = T>
    using addition_group_concept = is_detected<addition_group_adapter, T, U>;

,这有效(!)。这也是与C ++ 20概念的语法最匹配的替代方法。甚至可以使用宏来简化它,

#define SIMPLE_REQ(expression)\
    std::is_same<void_t<decltype(expression)>,void>

template <typename T, typename U = T>
auto addition_group (T t, U u) -> requirement_seq
    <
    SIMPLE_REQ(t + u),
    SIMPLE_REQ(t - u),
    SIMPLE_REQ(+t),
    SIMPLE_REQ(-t),
    SIMPLE_REQ(+u),
    SIMPLE_REQ(-u)
    >;

我想知道这里人们对这种方法的看法是什么。潜在的缺点是什么?可以做得更好或更简洁吗?

我也很想传达“粒状”编译器错误消息。目前,我收到的错误消息是我在 static_assert add 函数的中写的一条,这并不能准确地告诉我我的要求序列中的哪些要求失败了。

I'm coding in C++14 a program that uses template classes and functions. I don't like the compiler error messages I get when the template types don't fulfill the requirements I ask for with std::enable_if

I wondered if I could do better trying to emulate some functionality of C++20 concepts. Here are a few ideas I had to define a "concept" that checks the validity of an arbitrary number of expressions given some template parameters

  • say I wanted to check if types T and U support the addition operator, I'd write a test using a template alias like this
    template <typename T, typename U = T>
    using addition_test = decltype(declval<T>() + declval<U>());
  • Then I'd define a simple requirement like this
    template <typename ...T> 
    using addition_req = is_detected<addition_test, T...> 
  • The is_detected is an alias for a member type of the detector struct used in the TSv2 detection idiom. In a nutshell, the detector attempts to instantiate addition_test with template arguments T..., if it fails it'll have a member type alias for std::false_type, otherwise it'll have a member type alias for std::true_type.

  • I define my concept using the conjunction logical operations on traits introduced in c++17. The addition_group_concept below will be an alias for std::true_type if all my requirements are satisfied, or an alias for std::false_type otherwise.

template <typename T, typename U = T>
using addition_group_concept = conjunction <
        addition_req<T, U>,
        subtraction_req<T, U>,
        unary_plus_req<T>,
        unary_minus_req<T>
>;
  • Finally, I can use my "concept", like this
template <typename T, typename U>
auto add (T const & t, U const & u) ->
std::enable_if_t<addition_group_concept<T, U>::value, decltype(t + u)>
{
    return t + u;
}

template <typename T, typename U>
auto add (T const & t, U const & u) ->
std::enable_if_t<!addition_group_concept<T, U>::value, void>
{
    static_assert(always_false<T>::value,
                                "The operands must support addition and substraction operations.");
}

If my concept is not satisfied, I will get a compiler error message that is somewhat shorter and less cryptic than your usual failed template substitution message. So far so good (?)... but is far too much boilerplate code

I thought perhaps I could write a dummy function with a trailing return type, and put my requirements sequence in an std::enable_if in the trailing return type. Then I could define my concept with the help of template alias that is used as an adapter. Like this

// void_t is the c++17 void_t defined as:
// template <typename ...T>
// using void_t = void;

    template <typename ...Requirements>
    using requirement_seq = std::enable_if_t<conjunction<Requirements...>::value>;

    template <typename T>
    using simple_req = std::is_same<void_t<T>, void>;

    template <typename T, typename U = T>
    auto addition_group (T t, U u) -> requirement_seq<
        simple_req<decltype(t + u)>,
        simple_req<decltype(t - u)>,
        simple_req<decltype(+t)>,
        simple_req<decltype(-t)>,
        simple_req<decltype(+u)>,
        simple_req<decltype(-u)>
        >;

    template <typename ...T>
    using addition_group_adapter = decltype(addition_group<T...>);

    template <typename T, typename U = T>
    using addition_group_concept = is_detected<addition_group_adapter, T, U>;

And this works (!). And it's also the alternative that most closely matches the syntax of C++20 concepts. It could even be streamlined using a macro

#define SIMPLE_REQ(expression)\
    std::is_same<void_t<decltype(expression)>,void>

template <typename T, typename U = T>
auto addition_group (T t, U u) -> requirement_seq
    <
    SIMPLE_REQ(t + u),
    SIMPLE_REQ(t - u),
    SIMPLE_REQ(+t),
    SIMPLE_REQ(-t),
    SIMPLE_REQ(+u),
    SIMPLE_REQ(-u)
    >;

I wonder what people's opinions here are regarding this approach. What could be the potential shortcomings? Could this be done better or more succinctly?

I'd also love to have "granular" compiler error messages. At the moment, the error message I get is the one I write inside the static_assert of my add function, which doesn't tell me precisely which requirement failed in my requirement sequence.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文