【问题标题】:How to ensure an accepted C++ template type to overload an operator?如何确保接受的 C++ 模板类型重载运算符?
【发布时间】:2025-12-05 07:10:01
【问题描述】:

我要实现一个类模板:

template <typename Type>
class MyClass {/*...*/}

我想强制MyClass 接受的Type 重载&lt; 运算符。我怎样才能做到这一点?这应该是可能的,因为标准库正在为已排序的容器执行此操作。我不想使用显式模板专业化并在MyClass 中实现特定类型。我希望Type 负责。

【问题讨论】:

    标签: c++ templates


    【解决方案1】:

    简单的方法就是使用它。 C++ 模板完全是鸭子类型的,在您传入实际类型之前不会进行检查。

    您可以使用基于 SFINAE 的技术来测试类似的东西...

    namespace details {
      template<template<class...>class Z, class, class...>
      struct can_apply:std::false_type{};
      template<class...>struct voider {using type=void;};
      template<class...Ts>using void_t=typename voider<Ts...>::type;
      template<template<class...>class Z, class...Ts>
      struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{};
    }
    template<template<class...>class Z, class...Ts>
    using can_apply = details::can_apply<Z,void,Ts...>;
    

    这是一个 SFINAE 帮助器,用于检测您是否可以将模板应用于一组类型。

    template<class X, class Y=X>
    using raw_less_r = decltype( std::declval<X>()<std::declval<Y>() );
    
    template<class X, class Y=X>
    using can_raw_less = can_apply<raw_less_r, X, Y>;
    

    如果int &lt; int 有效,则can_raw_less&lt;int&gt; 派生自true_type,否则false_type

    然后我们开始:

    template <typename Type, class=void>
    class MyClass;
    
    template <typename Type>
    class MyClass<Type, std::enable_if_t<can_raw_less<Type>>>
    {/*...*/}
    

    使用 SFINAE 和 enable_if_t 创建一个当且仅当 Type&lt;Type 有效时才存在的特化。

    请注意,更好的模式可能是:

    template<class F, class...Args>
    using invoke_result = decltype( std::declval<F>()( std::declval<Args>()... ) );
    template<class F, class...Args>
    using can_invoke = can_apply< invoke_result, F, Args... >;
    
    template<class Type, class Cmp=std::less<Type>>
    class MyClass {
      static_assert( can_invoke< Cmp, Type const&, Type const& >{}, "You must be able to compare Type to itself" );
      // use Cmp{}(Type, Type) to compare
    };
    

    这就是标准容器的作用,并添加了static_assert 以获取更清晰的错误消息。

    【讨论】:

    • 或者直接使用boost::has_less
    • 这肯定会吓到任何新来者 :) 一个解决方案,尽管如此。
    • 谢谢先生!这让我开始在这里阅读一些关键字。
    【解决方案2】:

    如果MyClass 的实现对Type 的对象使用&lt; 运算符,则您无需执行任何操作。如果不支持该操作,编译器会报错。

    如果MyClass 的实现不对Type 的对象使用&lt; 运算符,则无需担心。

    在任何一种情况下,除了记录Type 的预期之外,您无需做任何事情。

    【讨论】:

    • 非常感谢先生!
    • 但我在容器规范运算符中看到了 std::less<_kty>。他们为什么在那里?
    • @lulijeta,允许使用为类型自动定义的operator&lt;。但是,可以通过使用可以使用不同逻辑的自定义 clas 来使用不同的方法对容器的项目进行排序,例如 std::greater
    • 您可以检测是否支持&lt;,如果不支持则生成错误,而不是如果您不明确执行则会出现错误。
    • @Yakk-AdamNevraumont 如何检测特定方法或运算符是否不存在?
    【解决方案3】:

    使用来自Boost.TypeTraitsboost::has_less 简单易读:

    #include <boost/type_traits/has_less.hpp>
    
    template <typename Type>
    class MyClass {
        static_assert( boost::has_less<Type>::value, 
                       "Template parameter Type must be less-than-comparable" );  
    };
    

    Live Demo

    【讨论】: