Изведете типа въз основа на наличието на черта

Следното репо се опитва да вземе 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
Можете да използвате C++ идиома на Member Detector: en.wikibooks.org/wiki/More_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