使用std :: conditional和不可转换的类型(原始vs指针)

时间:2019-04-06 20:04:23

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

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

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

3 个答案:

答案 0 :(得分:2)

问题是,当你写

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进行二传手,但无济于事...

这是因为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>

您得到一个错误。

答案 1 :(得分:1)

一种更简单的解决方案是提供由<select> <option>random text</option> -->There should be also an image </select> 区分的不同基类:

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实现为非动态方法,反之亦然。

答案 2 :(得分:1)

这个小工具:

#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); } )();
}
当使用编译时常量std::integral_constant<T, t>调用

dispatch时,它返回其第n个参数。如果您通过std::true_type,即std::integral_constant<bool, true>

std::is_pointer<T>继承自true_typefalse_type

然后,我们传递2个lambda。在编译时选择一个。然后,我们运行返回值。然后,它返回一个double或指向double的指针。

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

中,这变得容易得多,但是我会尽力而为。并且调度可以很好地解决中的此类问题。