Вывести тип на основе существования признака

Следующий репозиторий пытается взять std::tuple и повторить его для вывода различных значений, связанных с ним. std::tuple — это вершина, и конечным ее использованием будет вызов glEnableVertexArray и glVertexAttribPointer для элементов.

До сих пор я выполнял итерацию типов компонентов кортежа, а также находил смещение внутри каждого кортежа для каждого элемента. Однако я застрял с этой функцией:

template<class T>
void EmitAttribute(T const & v, int stride, int offset, int i)
{
    std::cout << "Stride is " 
              << stride 
              << " element index " 
              << i 
              << " is at offset " 
              << offset 
              << " has 1 component " 
              << std::endl;
}

Для базовых типов (не структур) я хочу вывести «имеет 1 компонент». Для элементов с чертой num_components я хочу выделить количество компонентов. Я старался:

template<class T, class S>
void EmitAttribute(T<S> const & v, int stride, int offset, int i)
{
    ...
              << " has " << T::num_components << " components " 
    ...     
}

Но он не компилируется. Как мне написать шаблон так, чтобы одна функция вызывалась, когда T не имеет трейта num_components, а другая вызывалась, когда он есть?

Полное репо:

#include <iostream>
#include <tuple>

template<class T, int C>
struct vec
{
    typedef T value_type;

    enum { num_components = C };
};

template<class T>
struct vec2 : vec<T, 2>
{
public:

    T x, y;
    vec2(T X, T Y) : x(X), y(Y) {}
};

template<class T>
struct vec3 : vec<T, 3>
{
public:

    T x, y, z;
    vec3(T X, T Y, T Z) : x(X), y(Y), z(Z) {}
};

template<class T>
struct vec4 : vec<T, 4>
{
public:

    T x, y, z, w;
    vec4(T X, T Y, T Z, T W) : x(X), y(Y), z(Z), w(W) {}
};

namespace VertexAttributes
{
    template<class T>
    void EmitAttribute(T const & v, int stride, int offset, int i)
    {
        std::cout << "Stride is " 
                  << stride 
                  << " element index " 
                  << i 
                  << " is at offset " 
                  << offset 
                  << " has 1 component " 
                  << std::endl;
    }

    template<int index, class T>
    int ElementOffset(T & t)
    {
        return static_cast<int>(reinterpret_cast<char*>(&std::get<index>(t)) - reinterpret_cast<char*>(&t));
    }

    template<int index, typename... Ts>
    struct Emitter {
        void EmitAttributes(std::tuple<Ts...>& t, unsigned size) {
            EmitAttribute(std::get<index>(t), size, ElementOffset<index>(t), index);
            Emitter <index - 1, Ts...> {}.EmitAttributes(t, size);
        }
    };

    template<typename... Ts>
    struct Emitter < 0, Ts... > {
        void EmitAttributes(std::tuple<Ts...>& t, unsigned size) {
            EmitAttribute(std::get<0>(t), size, ElementOffset<0>(t), 0);
        }
    };

    template<typename... Ts>
    void EmitAttributes(std::tuple<Ts...>& t) {
        auto const size = std::tuple_size<std::tuple<Ts...>>::value;
        Emitter < size - 1, Ts... > {}.EmitAttributes(t, sizeof(std::tuple<Ts...>));
    }
}

int main()
{
    typedef std::tuple<vec2<float>, vec3<double>, vec4<float>> vertexf;
    typedef std::tuple<vec2<double>, vec3<float>, vec4<double>> vertexd;
    typedef std::tuple<int, vec3<unsigned>, double> vertexr;

    vertexf vf = std::make_tuple(vec2<float>(10, 20), vec3<double>(30, 40, 50), vec4<float>(60, 70, 80, 90));
    vertexd vd = std::make_tuple(vec2<double>(10, 20), vec3<float>(30, 40, 50), vec4<double>(60, 70, 80, 90));
    vertexr vr = std::make_tuple(100, vec3<unsigned>(110, 120, 130), 140.5);

    VertexAttributes::EmitAttributes(vf);
    VertexAttributes::EmitAttributes(vd);
    VertexAttributes::EmitAttributes(vr);

    return 0;
}

person Robinson    schedule 01.07.2015    source источник
comment
Почему бы не использовать только один класс и использовать T data[N] в качестве члена данных вместо T data1, data2, data3, .... dataN? Кроме того, изучите шаблон с переменным числом аргументов и std::initializer_list.   -  person Nawaz    schedule 01.07.2015
comment
Я думаю, вы предлагаете мне создать vec‹T, 1›? Так бы и было, да. Что касается того, как данные хранятся для каждого элемента, у меня есть выбор. Я могу использовать std::array, отдельные компоненты или их объединение. Просто приятно иногда писать v[1], а иногда v.y.   -  person Robinson    schedule 01.07.2015
comment
Если вам также нужны хорошие имена, вы можете использовать x(), y(), z() в качестве функций-членов, которые static_assert измеряют проверку достоверности, а затем используют соответствующий индекс для возврата запрошенного значения.   -  person Nawaz    schedule 01.07.2015
comment
Хорошо, есть разные способы, которыми я могу представить фактические компоненты вектора, но это не вопрос здесь, поскольку они не упоминаются в репо (полагаю, я мог бы сделать более простое репо).   -  person Robinson    schedule 01.07.2015
comment
Вы можете использовать идиому Member Detector C++: 2B%2B_Idioms/Member_Detector   -  person Serge Rogatch    schedule 01.07.2015


Ответы (1)


Вы можете создать черты

namespace detail
{
    template <typename T>
    decltype(T::num_components, void(), std::true_type{}) has_num_components_impl(int);

    template <typename T>
    std::false_type has_num_components_impl(...);
}

template <typename T>
using has_num_components = decltype(detail::has_num_components_impl<T>(0));

а затем используйте SFINAE или диспетчеризацию тегов:

template <typename T>
std::enable_if_t<!has_num_components<T>::value, std::size_t>
get_num_components() { return 1; }

template <typename T>
std::enable_if_t<has_num_components<T>::value, std::size_t>
get_num_components() { return T::num_components; }

И наконец:

template<class T>
void EmitAttribute(T const & v, int stride, int offset, int i)
{
    std::cout << "Stride is "
              << stride
              << " element index "
              << i
              << " is at offset "
              << offset
              << " has "
              << get_num_components<T>()
              << " component "
              << std::endl;
}

Демонстрация в реальном времени

person Jarod42    schedule 01.07.2015
comment
из интереса, почему decltype имеет предложение void() в качестве второго аргумента? - person Richard Hodges; 01.07.2015
comment
@RichardHodges: для обработки злого случая с возможной перегрузкой оператора комы num_components. - person Jarod42; 01.07.2015
comment
Ага! Спасибо. Это самое замечательное в C++, всегда можно открыть что-то новое! - person Richard Hodges; 01.07.2015
comment
Я думаю, что сегодня я столкнулся с некоторым несоответствием (или текущим состоянием C++ 11) в Visual Studio, потому что ваш пример дает мне ошибки, т.е. 'VertexAttributes::has_num_components': должен быть классом или пространством имен, когда с последующим '::'. - person Robinson; 01.07.2015
comment
Спасибо за ваши старания. Однако Visual Studio 2013 не любит диспетчеризацию тегов. Я получаю ошибку независимо (с вашей демонстрацией тоже). has_num_components возвращает правильный тип (true или false), а это уже что-то. Во всяком случае, я думаю, что понимаю, как все это работает, поэтому я полагаю, что это просто вопрос того, чтобы сделать что-то еще, пока MS не догонит. - person Robinson; 01.07.2015
comment
Ну что строит. Хороший. Спасибо. - person Robinson; 01.07.2015