Использование std :: условного с неконвертируемыми типами (raw против указателя) - PullRequest
0 голосов
/ 06 апреля 2019

Я пытаюсь написать шаблон класса, который выбирает хранить переменную-член raw или как указатель в зависимости от аргумента шаблона MAX_SIZE. Когда я пытаюсь создать экземпляр с размером 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_' фактически является частью объединения (наряду с char, int, bool, float и т. Д.).

Я пытался делать сеттеры с 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 ]

2 голосов
/ 06 апреля 2019

Проблема в том, что когда пишешь

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 голос
/ 07 апреля 2019

Эта маленькая утилита:

#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<T, t> возвращает свой n-й аргумент. Если вы передадите его std::true_type, это будет std::integral_constant<bool, true>.

std::is_pointer<T> наследуется от true_type или false_type.

Затем мы отправляем 2 лямбды. Во время компиляции один выбирается. Затем мы запускаем возвращаемое значение. Затем он возвращает либо double, либо указатель на double.

То, какой возвращается, определяется во время компиляции, это прекрасно работает.

В и это становится намного проще, но я работаю с тем, что у меня есть. И диспетчеризация работает очень хорошо, чтобы решить эту проблему в .

1 голос
/ 06 апреля 2019

Гораздо более простым решением было бы предоставить другой базовый класс, дифференцированный по 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 как нединамический подход илинаоборот.

...