Search code examples
c++type-traitstype-deduction

Deduce type based on existence of a trait


The following repo is attempting to take a std::tuple and iterate it to output various values associated with it. The std::tuple is a vertex and the end use of this will be to call glEnableVertexArray and glVertexAttribPointer on the elements.

So far I've got iterating the tuple's component types working, as well as finding the offset within each tuple for each element. However I get stuck with this function:

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;
}

For basic types (non-structs), I want to emit " has 1 component". For elements with a num_components trait I want to emit the number of components. I tried:

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

But it doesn't compile. How do I write the template such that one function is called when T doesn't have a num_components trait and the other is called when it does?

Full repo:

#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;
}

Solution

  • You may create a traits

    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));
    

    and then use SFINAE or tag dispatching:

    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; }
    

    And finally:

    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;
    }
    

    Live Demo