【问题标题】:How to arbitrarily enable or disable class method based on specific type?如何根据特定类型任意启用或禁用类方法?
【发布时间】:2017-04-11 11:21:27
【问题描述】:

我有这样的课:

struct X
{
    enum Type { INT, FLOAT };
    using val_t = std::tuple<int, float>;

    X(Type t) : type(t) {}

    Type type;

    template<typename T>
    X& operator =(T x)
    {
        // ???
        static_assert(T is the same as `type');

        // ???
        std::get<type>(val) = x;

        return *this;
    }

    val_t val;
};

如果用户尝试分配不兼容的值,是否可以在编译时断言?

例如:

X x1(X::INT);
x1 = 5; // OK
x1 = 3.14; // compilation error

注意:我更喜欢将类保留为不是模板,因为我需要将其实例保留在集合中(例如 std::vector 等)。

【问题讨论】:

  • 如果模板类而不是方法,是的,可能会出现编译时错误。
  • std::is_same 检查 T 是否为 float/int/whatever en.cppreference.com/w/cpp/types/is_same
  • @Jarod42,我编辑了我的问题,提到我不想将其保留为模板,因为该类的实例保存在 std::vector 中。
  • X x1(rand() ? X::INT : X::FLOAT); x1 = 5.2; - 确定还是错误?
  • @Raxvan,你能提供一个代码示例吗?谢谢。

标签: c++ templates sfinae enable-if


【解决方案1】:

你不能:type_ 的值是运行时数据,编译错误不是在运行时确定的。

你可以这样做:

enum Type { INT, FLOAT };
template<Type type_>
struct X {
  using val_t = std::tuple<int, float>;


  template<typename T>
  X& operator =(T x) {
    // ???
    static_assert((type_==INT&&std::is_same<T,int>{})||(type_==FLOAT&&std::is_same<T,float>{}),
      "types do not match"
    );
    std::get<T>(val) = x;

    return *this;
  }

  val_t val;
};
X<INT> x1;
x1 = 5; // OK
x1 = 3.14; // compilation error

但我觉得没什么意义。

一种方法是有一个不进行检查的基类型只存储状态,以及一个知道其类型的派生类型。

struct Base{
  enum {INT,FLOAT} Type;
  // etc
};
template<Base::Type type>
struct Derived:private Base{
  Derived():Base(type){}
  using Base::some_method; // expose base methods
  Base& get_base()&{return *this;}
  Base get_base()&&{return std::move(*this);}
  Base const& get_base()const&{return *this;}

  template<class T>
  Derived& operator=( T o){
    static_assert((type_==INT&&std::is_same<T,int>{})||(type_==FLOAT&&std::is_same<T,float>{}),
      "types do not match"
    );
    Base::operator=(std::move(o));
    return *this;
  }
};

Base 不检查,充其量它运行时断言。 Derived 在编译时检查。

当你在编译时静态知道类型时,你使用Derived&lt;INT&gt; d;;当您不需要或需要忘记时,请使用.get_base()Base b(type_enum_val);

【讨论】:

  • 我相信有机会实现它,因为X的类型可以在编译时知道。我尝试使用constexpr,但找不到任何可行的解决方案。
  • @AlexBlekhman:X 的类型是 X。这就是 C++ 类型系统的工作方式。如果constexpr 可以工作,那么template 可以工作。在幕后,它们密切相关。问题是你无法做出选择。 X&lt;int&gt;X&lt;float&gt; 的类型不同,或者只有一个 X 类型。
  • @alex 有这样的“机会”,但它涉及到模板非类型参数,而不是构造函数参数。除非具有自动定理检查的类似 coq 的前置条件后置条件系统以及将所述证明传递给参数调用的能力,否则您不会在 告诉编译器在运行时会发生变化的数据上获得编译时断言。对于 C++ 中的变量实例,不会“在运行时发生变化”的数据在其 type 中进行编码。基于“运行时”(好吧,在编译时运行)状态,我们可以让表达式无法成为 constexpr,但这不是您所要求的。
  • 我的意思是在 C++17 中我可以将 ctor 参数的类型转换为实例中的类型数据,但这只是语法糖。
  • @Yakk,我明白你的意思。同意,在 C++ 的这个阶段似乎是不可能的。我希望他们为语言添加某种基本的反射。
【解决方案2】:

考虑到您有 Type type;,如果 type 是 INT 或 FLOAT 或您拥有的任何东西,您无法在编译时断言。对于该检查,您只能在运行时断言。

对于其他一切,您可以进行编译时检查,以及使用某些模板元编程的运行时检查:

namespace detail_tuple
{
    template <typename T, std::size_t N, typename... ARGS>
    struct get_by_type_impl {
        enum {
            kIdx = N
        };
    };
    template <typename T, std::size_t N, typename... ARGS>
    struct get_by_type_impl<T, N, T, ARGS...> {
        enum {
            kIdx = N
        };
    };
    template <typename T, std::size_t N, typename U, typename... ARGS>
    struct get_by_type_impl<T, N, U, ARGS...> {
        enum {
            kIdx = get_by_type_impl<T, N + 1, ARGS...>::kIdx
        };
    };
}
template <typename, typename>
struct validator;
template <typename T, typename... ARGS>
struct validator < T, std::tuple<ARGS...> >
{
    static void validate(const std::size_t type_idx)
    {
        //compiletime checks
        //get index of type T in ARGS...
        constexpr auto ind = detail_tuple::get_by_type_impl<T, 0, ARGS...>::kIdx;
        //check if index is valid
        static_assert(ind < sizeof...(ARGS), "Type index out of bounds, type T is was not found in the tuple!");

        //runtime checks
        if (type_idx != ind)
            std::cout << "Incompatible type index!\n";
    }
};

struct X
{
    enum Type
    {
        INT = 0,
        FLOAT,
        TYPE_COUNT,
    };
    using val_t = std::tuple<int, float>;


    X(Type t) : type(t) {}

    Type type;

    template<typename T>
    X& operator =(const T& x)
    {
        validator<T, val_t>::validate(type);

        std::get<T>(val) = x;
        return *this;
    }

    val_t val;
};

因为 std::get 使用类型 T 而不是 type

【讨论】:

  • 请注意,这会禁用 SFINAE 检查。如果您有一个泛型类型并且您想查看foo = bar 是否有效,那么您现在会遇到一个硬错误而不是没有答案。
  • 很抱歉,这行不通。 static_assert 应该检查用于构造 X 的特定类型。我提供了分配给不同类型会导致编译错误的示例。在您的示例中,我可以同时分配 intfloat 值而不会出现编译错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-12
  • 1970-01-01
  • 2011-04-29
  • 2020-10-15
相关资源
最近更新 更多