【问题标题】:Using std::conditional with non-convertible types (raw vs pointer)将 std::conditional 与不可转换类型一起使用(原始与指针)
【发布时间】:2019-04-06 20:04:22
【问题描述】:

我正在尝试编写一个类模板,该模板根据MAX_SIZE 模板参数选择存储成员变量 raw 或作为指针。 当我尝试使用足够大的MAX_SIZE 实例化它以选择原始时,编译器仍会尝试编译我将值分配为指针的行并触发不可能的转换错误 - 即使它可能会优化这条线在某个时候。

template <int MAX_SIZE = sizeof(void*)>
class WeakVar {
    typedef typename std::conditional<sizeof(long double) <= MAX_SIZE, long double, long double*>::type TypeLD;
    TypeLD ld_;

public:
    WeakVar() {
        if(std::is_pointer<TypeLD>::value)
            ld_ = new long double(0); // error: cannot convert 'long double*' to 'TypeLD'{aka 'long double'}
        else
            ld_ = 0;
    }
    //...
};

该类旨在成为节省空间的“弱类型变量”(不一定是速度高效的,它不意味着经常被调用)。 'ld_' 成员实际上是联合的一部分(以及 charintboolfloat 等...)。

我尝试使用std::enable_if 制作二传手,但无济于事...

//...
WeakVar() { setLD(0); }

typename std::enable_if<std::is_pointer<TypeLD>::value>::type setLD(long double value) {
    ld_ = new long double(value);
}
typename std::enable_if<!std::is_pointer<TypeLD>::value>::type setLD(long double value) {
    ld_ = value;
}
// error: the second method cannot be overloaded with the first
//...

有没有办法做到这一点? (同时保留选择班级MAX_SIZE的可能性)

【问题讨论】:

    标签: c++ c++11 templates template-meta-programming sfinae


    【解决方案1】:

    问题是当你写的时候

    WeakVar() {
        if(std::is_pointer<TypeLD>::value)
            ld_ = new long double(0); // error: cannot convert 'long double*' to 'TypeLD'{aka 'long double'}
        else
            ld_ = 0;
    }
    

    编译器必须编译if()两种情况

    所以当TypeLD 不是指针时,编译器也必须编译

     ld_ = new long double(0);
    

    解决方案:如果(何时)可以使用 C++17 或更高版本,请使用 if constexpr

        if constexpr (std::is_pointer<TypeLD>::value)
            ld_ = new long double(0);
        else
            ld_ = 0;
    

    当测试的值在编译时已知时,它被引入不编译错误代码。

    否则(在 C++11 和 C++14 中)您可以编写两个不同的函数,并使用 SFINAE 启用正确的一个。

    举例(注意:代码未测试)

    template <typename T = TypeLD,
              typename std::enable_if<true == std::is_pointer<T>::value, bool>::type = true>
    WeakVar () : ld_{new long double(0)}
     { }
    
    template <typename T = TypeLD,
              typename std::enable_if<false == std::is_pointer<T>::value, bool>::type = true>
    WeakVar () : ld_{0}
     { }
    

    我尝试使用 std::enable_if 制作 setter,但无济于事...

    这是因为 SFINAE 要启用/禁用类的方法,仅适用于模板方法,该方法对方法本身的模板参数进行测试,而不是对类的模板参数进行测试。

    所以,在上面的例子中,我写了

    template <typename T = TypeLD,
              typename std::enable_if<true == std::is_pointer<T>::value, bool>::type = true>
    

    所以启用/禁用测试是关于类型名T,构造函数的模板参数,而不是关于TypeLD,完整类的模板参数。

    如果你写

    template <typename std::enable_if<true == std::is_pointer<TypeLD>::value, bool>::type = true>
    

    你得到一个错误。

    【讨论】:

    • enable_if 中为什么写true == std::is_pointer&lt;T&gt;::value 而不仅仅是std::is_pointer&lt;T&gt;::value?同样,在false 的情况下,它可能只是!std::is_pointer&lt;T&gt;::value
    • @Kyle - 这是我的问题:我很难一眼看出condition!condition 之间的区别。我发现它更清晰、更直接地看到了 truefalse 的区别。
    【解决方案2】:

    一个更简单的解决方案是提供一个由std::conditional 区分的不同基类:

    class WeakVarA {};
    class WeakVarB {};
    
    template <int MAX_SIZE = sizeof(void*)>
    class WeakVar 
        : public typename std::conditional<sizeof(long double) <= MAX_SIZE, WeakVarA, WeakVarB>::type 
    {
        // ...
    };
    

    然后只需将WeakVarA 实现为动态方法,将WeakVarB 实现为非动态方法,反之亦然。

    【讨论】:

      【解决方案3】:

      这个小工具:

      #define RETURNS(...) \
        noexcept(noexcept(__VA_ARGS__)) \
        -> decltype(__VA_ARGS__) \
        { return __VA_ARGS__; }
      
      template<class S, class F0, class...Fs>
      auto dispatch( std::integral_constant<S, S(0)>, F0&& f0, Fs&&... )
      RETURNS( dispatch( std::forward<F0>(f0) ) )
      
      template<class S, S s, class F0, class...Fs>
      auto dispatch( std::integral_constant<S, s>, F0&&, Fs&&...fs )
      RETURNS( dispatch( std::integral_constant<S, S(s-1)>{}, std::forward<Fs>(fs)... ) )
      
      template<std::size_t N, class...Fs>
      auto dispatch( Fs&&...fs )
      RETURNS( dispatch( std::integral_constant<std::size_t, N>{}, std::forward<Fs>(fs)... ) )
      

      可以帮忙。

      它会进行编译时切换。

      WeakVar() {
          ld_ = dispatch(std::is_pointer<TypeLD>{}, []{ return 0.; }, []{ return new long double(0); } )();
      }
      

      dispatch,当使用编译时常量 std::integral_constant&lt;T, t&gt; 调用时,返回其第 n 个参数。如果你通过std::true_type,那就是std::integral_constant&lt;bool, true&gt;

      std::is_pointer&lt;T&gt; 继承自 true_typefalse_type

      然后我们通过 dispatch 2 lambdas。在编译时选择一个。然后我们运行返回值。然后它返回一个双精度数或指向双精度数的指针。

      因为返回哪个是在编译时确定的,所以可以正常工作。

      中,这变得容易多了,但我会使用我得到的东西。并且 dispatch 在 中解决此类问题非常有效。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-05-19
        • 1970-01-01
        • 2015-07-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多