【问题标题】:Deduce type based on existence of a trait根据特征的存在推断类型
【发布时间】:2015-09-18 04:11:57
【问题描述】:

以下 repo 正在尝试获取 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;
}

【问题讨论】:

  • 为什么不只使用一个类,并使用T data[N] 作为数据成员,而不是T data1, data2, data3, .... dataN?另外,探索可变参数模板和std::initializer_list
  • 我想你是在建议我创建一个 vec?这样就可以了,是的。关于如何为每个元素存储数据,我有选择。我可以使用 std::array 或单个组件或两者的联合。有时写 v[1] 有时写 v.y. 很好。
  • 如果你也需要好的名字,那么你可以将x()y()z()作为成员函数,static_assert维度来检查有效性,然后使用适当的索引返回要求的值。
  • 好的,我可以通过多种方式表示向量的实际组件,但这不是这里的问题,因为它们没有在 repo 中引用(我想我可以制作一个更简单的 repo)。
  • 您可以使用“成员检测器”C++ 成语:en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Member_Detector

标签: c++ typetraits type-deduction


【解决方案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;
}

Live Demo

【讨论】:

  • 出于兴趣,为什么decltype的第二个参数是void()子句?
  • @RichardHodges:用num_components 的可能重载 coma 运算符来处理邪恶的情况。
  • 啊哈!谢谢你。这就是 C++ 的伟大之处,总有新的发现!
  • 我想我今天在 Visual Studio 中反对一些不合规性(或 C++ 11 的当前状态),因为你的例子给了我错误,即“'VertexAttributes::has_num_components ': 后跟 '::' 时必须是类或命名空间。
  • 感谢您的努力。然而,Visual Studio 2013 不喜欢标签调度。无论如何我都会收到错误消息(也有你的演示)。 has_num_components 返回正确的类型(真或假),这就是东西。无论如何,我想我明白这一切是如何运作的,所以我想这只是在 MS 赶上之前做其他事情的问题。
猜你喜欢
  • 2020-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多