Разъяснение по специализации шаблона функции-члена с помощью enable_if - PullRequest
0 голосов
/ 07 апреля 2019

Я хотел бы понять, где я ошибаюсь, пытаясь свести к минимуму многословность специализации шаблонов моих функций-членов. Я получаю ошибки компиляции при этом довольно произвольно. Вот версия, которая работает, которая, надеюсь, проливает свет на то, чего я пытаюсь достичь:

#include <iostream>
#include <type_traits>


typedef int i32;
template<class T>
struct rtvec
{
private:
    T* e;
    i32 d;

public:
    rtvec(i32 d) : d(d), e(new T[d]) {}
    //template<typename Args...>
    //rtvec()
    rtvec(const rtvec& in) : d(in.d), e(new T[in.d])
    {
        for (i32 i = 0; i < d; ++i)
            at(i) = in.at(i);
    }

    rtvec(rtvec<typename std::remove_pointer_t<T>>& in) : d(in.dim()), e(new T[in.dim()])
    {
        for (i32 i = 0; i < d; ++i)
            e[i] = &in.at(i);
    }
    ~rtvec() { delete[] e; }
    i32 dim() const { return d; }

    template<typename U=T, 
        typename std::enable_if_t<std::is_same_v<U,T>>* = nullptr,
        typename std::enable_if_t<!std::is_pointer_v<U>>* = nullptr>
    inline T& at(i32 i) 
    {
        return e[i];
    }

    template<typename U = T,
        typename std::enable_if_t<std::is_same_v<U, T>>* = nullptr,
        typename std::enable_if_t<std::is_pointer_v<U>>* = nullptr>
        inline typename std::remove_pointer_t<T>& at(i32 i)
    {
        return *e[i];
    }

};


int main()
{
    rtvec<float> v(2);
    v.at(0) = 1;
    v.at(1) = 2;
    rtvec<float*> p = v;
    p.at(0) = 5;
    std::cout << v.at(0) << " " << v.at(1) << "\n";
    return 0;
}

По сути, я пытаюсь создать класс размерного вектора переменной времени выполнения, который при создании экземпляра с указателем можно использовать как своего рода ссылку на вектор того же типа (точнее, у меня есть несколько массивов каждой из координат из набора точек, и я хочу использовать «опорный» вектор, чтобы иметь возможность работать с ними, как если бы они были упорядочены в памяти наоборот). Однако, когда я пытаюсь упростить код, пытаясь удалить то, что я считаю ненужным typename U. Я получаю следующую ошибку компиляции в MSVC2017: std::enable_if_t<false,void>' : Failed to specialize alias template. Вот менее многословная версия, к которой я стремился:

struct rtvec
{
private:
    T* e;
    i32 d;

public:
    rtvec(i32 d) : d(d), e(new T[d]) {}

    template<typename std::enable_if_t<!std::is_pointer_v<T>>* = nullptr>
    rtvec(const rtvec& in) : d(in.d), e(new T[in.d])
    {
        for (i32 i = 0; i < d; ++i)
            at(i) = in.at(i);
    }

    template<typename std::enable_if_t<std::is_pointer_v<T>>* = nullptr>
    rtvec(rtvec<typename std::remove_pointer_t<T>>& in) : d(in.dim()), e(new T[in.dim()])
    {
        for (i32 i = 0; i < d; ++i)
            e[i] = &in.at(i);
    }
    ~rtvec() { delete[] e; }
    i32 dim() const { return d; }


    template<typename std::enable_if_t<!std::is_pointer_v<T>>* = nullptr>
    inline T& at(i32 i)
    {
        return e[i];
    }

    template<typename std::enable_if_t<std::is_pointer_v<T>>* = nullptr>
    inline typename std::remove_pointer<T>::type& at(i32 i)
    {
        return *e[i];
    }

};

Однако, если я немного изменю это, он скомпилируется:

template<class T>
struct rtvec
{
private:
    T* e;
    i32 d;

public:
    rtvec(i32 d) : d(d), e(new T[d]) {}

    template<typename std::enable_if_t<!std::is_pointer_v<T>>* = nullptr>
    rtvec(const rtvec& in) : d(in.d), e(new T[in.d])
    {
        for (i32 i = 0; i < d; ++i)
            at(i) = in.at(i);
    }

    /*template<typename std::enable_if_t<std::is_pointer_v<T>>* = nullptr>
    rtvec(rtvec<typename std::remove_pointer_t<T>>& in) : d(in.dim()), e(new T[in.dim()])
    {
        for (i32 i = 0; i < d; ++i)
            e[i] = &in.at(i);
    }*/
    ~rtvec() { delete[] e; }
    i32 dim() const { return d; }


    template<typename std::enable_if_t<!std::is_pointer_v<T>>* = nullptr>
    inline T& at(i32 i)
    {
        return e[i];
    }

    /*template<typename std::enable_if_t<std::is_pointer_v<T>>* = nullptr>
    inline typename std::remove_pointer<T>::type& at(i32 i)
    {
        return *e[i];
    }*/


};

(пока часть, связанная с указателем, тоже закомментирована в main). Я хочу понять, что делает второй код не компилируемым.

Причина, по которой я не специализируюсь непосредственно на классе, заключается в том, что в моей исходной реализации у меня есть много других функций-членов, которые будут эквивалентны между двумя специализациями, которые я не хочу повторять.

Ответы [ 2 ]

3 голосов
/ 07 апреля 2019

Однако, когда я пытаюсь упростить код, пытаясь удалить то, что я считаю ненужным

К сожалению (если я правильно понимаю) вы удалили что-то необходимое

Если я правильно понимаю, вы упростили следующие методы

template<typename U=T, 
    typename std::enable_if_t<std::is_same_v<U,T>>* = nullptr,
    typename std::enable_if_t<!std::is_pointer_v<U>>* = nullptr>
inline T& at(i32 i) 
{
    return e[i];
}

template<typename U = T,
    typename std::enable_if_t<std::is_same_v<U, T>>* = nullptr,
    typename std::enable_if_t<std::is_pointer_v<U>>* = nullptr>
    inline typename std::remove_pointer_t<T>& at(i32 i)
{
    return *e[i];
}

следующим образом

template<typename std::enable_if_t<!std::is_pointer_v<T>>* = nullptr>
inline T& at(i32 i)
{
    return e[i];
}

template<typename std::enable_if_t<std::is_pointer_v<T>>* = nullptr>
inline typename std::remove_pointer<T>::type& at(i32 i)
{
    return *e[i];
}

К сожалению, SFINAE работает с шаблонным методом только тогда, когда используются тесты (std::enable_if_t), основанные на шаблонных параметрах самого метода.

Я имею в виду: SFINAE не работает, когда в std::enable_if_t тестах задействовано T (и только T), потому что T является параметром шаблона структуры, а не параметром шаблона метода.

Так что вам нужно трюк

typename U = T

, которые "преобразуют" тип T в параметр шаблона метода.

Вы можете немного упростить удаление typename до std::enable_if_t, потому что "_t" в точности равно typename (см. Определение std::enable_if_t)

Не по теме: я не языковой слой, но, насколько я знаю,

 std::enable_if_t<std::is_same_v<U,T>>* = nullptr

не совсем законно; Я предлагаю переписать, используя int вместо void *

 std::enable_if_t<std::is_same_v<U,T>, int> = 0

или, может быть bool

 std::enable_if_t<std::is_same_v<U,T>, bool> = true

или другие целочисленные типы

В заключение, я предлагаю переписать ваш at() метод следующим образом

template <typename U = T, 
          std::enable_if_t<std::is_same_v<U, T>, int> = 0,
          std::enable_if_t<!std::is_pointer_v<U>, int> = 0>
inline T& at(i32 i) 
{
    return e[i];
}

template<typename U = T,
         std::enable_if_t<std::is_same_v<U, T>, int> = 0,
         std::enable_if_t<std::is_pointer_v<U>, int> = 0>
    inline typename std::remove_pointer_t<T>& at(i32 i)
{
    return *e[i];
}
1 голос
/ 07 апреля 2019

Есть несколько причин, и вам необходимо указать полную ошибку компиляции. Что касается вашего кода, вы, кажется, используете C ++ 17.

Например, в этом коде вы пытаетесь вернуть ссылку на объект, хранящийся в массиве, верно?:

inline typename std::remove_pointer<T>::type& at(i32 i) {
        return *e[i];
}

Может быть заменен на более STL-подобный код:

using reference = T&;
using const_reference = const T&;

reference at(i32 i) {
    return e[i];
}

const_reference at(i32 i) const {
    return e[i];
}

Или используйте auto:

auto at(i32 i) const {
    return e[i];
}

Так работает большинство контейнеров STL. Например, если вы получите доступ к std::vector<T*>, он вернет ссылку на T *, а не ссылку на данные, на которые указывает T.

Что касается техники SFINAE, которую вы используете, я не уверен, правильно ли она написана.

Например, загляните в эту запись , чтобы найти информацию о правильных способах написания условий для выбора конструкторов. Малое сумари:

template <typename = typename std::enable_if<... condition...>::type>
explicit MyAwesomeClass(MyAwesomeClass<otherN> const &);

Например, если вы хотите включить конструктор только для тех экземпляров, которые не содержат тип указателя:

template<typename = typename std::enable_if_t<!std::is_pointer_v<T>>>
explicit rtvec(const rtvec& in) : d(in.d), e(new T[in.d]) {
    for (i32 i = 0; i < d; ++i)
        at(i) = in.at(i);
}

Теперь, учитывая тот факт, что вы используете C ++ 17, вы можете constexpr if, который сделает вашу жизнь намного проще и справится с различными ситуациями. Примерно так, наверное:

template <typename U>
explicit rtvec(const rtvec<U>& in) : d(in.d), e(new T[in.d]) {    
     for (i32 i = 0; i < d; ++i){
         if constexpr (std::is_pointer<T>::value &&
                       std::is_pointer<U>::value) {
             // ...
         } else if constexpr (!std::is_pointer<T>::value &&
                       std::is_pointer<U>::value) {
             // ...
         } else {
             //  rest of possible combinations
         }
     }
}   
...