将相同的函数应用于每个数据成员 - 类型不同的转换排序

4
考虑以下结构体:
struct Test {
   char a;
   short b;
   int c;
   long long d;
   void transformTest() {
      // Pseudo
      foreach datamember (regardless of type) of Test
          call someTransform(datamember)
   }
};

我们也可以将lambda表达式、函数指针、仿函数等传递给transformTest()函数,但这并不是我现在所关注的问题。
那么最佳实现方式是什么?
4个回答

3

最好的方法是明确地执行:

someTransform(a);
someTransform(b);
someTransform(c);
someTransform(d);

当然,您需要适当数量的someTransform()的重载。
如果你真的不喜欢这个方法,总有Boost Fusion。使用它,您可以将值放在库可以理解和迭代的结构中。对于简单的用例,这样做可能没有价值。

谢谢,我会看看Boost Fusion,不幸的是我的情况并不简单 :(. - ScarletAmaranth
好的。一旦您教会Fusion如何查看您的数据,那么只需要使用http://www.boost.org/doc/libs/1_54_0/libs/fusion/doc/html/fusion/algorithm/iteration/functions/for_each.html函数进行迭代处理即可。 - John Zwinck
嗯,快速浏览后,我认为最终会将所有内容都包装在元组中,并编写模板机制来将所有内容解包到该调用中。 - ScarletAmaranth

3
听起来像是使用 Boost Fusion及其for_each()函数结合 BOOST_FUSION_ADAPT_STRUCT的案例。这些东西可以创造奇迹!以下是一个示例,让您了解如何完成它:
#include <boost/fusion/include/algorithm.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <iostream>

using namespace boost::fusion;

struct Print
{
    template <typename T>
    void operator()( T && t ) const
    {

        std::cout << t << ' ';
    }
};

struct Test {
    char a;
    short b;
    int c;
    long long d;

    void printTest() const;
};

BOOST_FUSION_ADAPT_STRUCT(
    Test,
    (char, a)
    (short, b)
    (int, c)
    (long long, d)
    )

void Test::printTest() const
{
    for_each ( *this, Print() );
}

int main()
{
    const auto t = Test();
    t.printTest();
}

1

虽然Boost.Fusion是一个不错的解决方案,但我认为在C++11中你可以像这样使用std::tuple

    template <unsigned ... indices>
    struct sequence
    {
        typedef sequence type;
    };

    template <unsigned size, unsigned ... indices>
    struct static_range : static_range<size-1,size-1,indices...> {};

    template <unsigned ... indices>
    struct static_range<0, indices...> : sequence<indices...> {};

    template <class Function, class Tuple, unsigned ... indices>
    auto transform_impl(const Tuple & t, Function f, sequence<indices...>) ->
        std::tuple<decltype(f(std::get<indices>(t)))...>
    {
         return std::make_tuple(f(std::get<indices>(t))...);
    }

    template <class Function, class Tuple>
    auto transform_tuple(const Tuple & t, Function f) ->
        decltype(transform_impl(t, f, static_range<std::tuple_size<Tuple>::value>()))
    {
        return transform_impl(t, f, static_range<std::tuple_size<Tuple>::value>());
    }
sequence/static_range类在我的代码中非常有用,可以通过索引扩展类(不仅仅是std::tuple),以便我可以使用std::get来获取它们。

除此之外,我认为代码相当简单明了,但需要注意的是,使用这种方法调用f函数的每个元组元素的顺序是未定义的。

使用方法如下:

    std::tuple<char, short, int, long long> t;
    struct addone
    { template <class T> auto operator()(T t) -> decltype(t+1) {return t + 1;}};
    auto t2 = transform_tuple(t, addone());

由于整数提升,生成的元组与输入元组类型不同,每个元素将具有类型typename std::common_type<T,int>::type

是的,这与我最终编写的代码非常相似。Boost Fusion 似乎会生成汇编代码,而我可以在编译时完成所有这些操作。我需要这个功能来确保我可以将东西输出到具有正确字节顺序的二进制文件中,因此我实际上进行了这种“序列化”。问题是我不能承担类型提升以正确交换字节(或在某些情况下根本不交换)的成本。但是,谢谢 :). - ScarletAmaranth
@ScarletAmaranth 类型提升只是一个例子,我希望你的用法与此不同。 - SirGuy
我刚才提到了它 :)。 - ScarletAmaranth

1
步骤1:将您的数据包装在一个元组中。可能是临时的。
步骤2:将您的可调用对象包装在一个函数对象中。
步骤3:编写一个tuple_foreach,对tuple的每个元素应用一个函数对象。
对于步骤1,我建议保留数据不动,只使用std :: tie创建引用的tuple。
对于步骤2,一个简单的完美转发函数对象如下:
#define RETURNS(X) ->decltype(X) { return (X); }
struct foo_functor {
  template<typename... Args>
  auto operator()(Args&&... args) const
    RETURNS( foo( std::forward<Args>(args)... ) )
};

这段文字是关于编程的。第一段讲的是一个名为foo的函数覆盖集合,将其封装成一个对象,自动将任何调用分派到适当的重载foo函数。第二段则介绍了如何使用索引技巧在tuple的每个元素上运行代码。请注意保留HTML标签,不进行解释。
void do_in_order() {}
template<typename Lambda, typename... Lambdas>
void do_in_order( Lambda&& closure, Lambdas&&... closures ) {
  std::forward<Lambda>(closure)();
  do_in_order( std::forward<Lambdas>(closures)... );
}
template<unsigned... Is>
struct seq { typedef seq<Is> type; }
template<unsigned Max, unsigned... Is>
struct make_seq:make_seq<Max-1, Max-1, Is...> {};
template<unsigned... Is>
struct make_seq<0,Is...>:seq<Is...> {};

template<typename Tuple, typename Functor, unsigned... Is>
void foreach_tuple_helper( seq<Is...>, Tuple&& t, Functor&& f ) {
  do_in_order(
    [&]{ std::forward<Functor>(f)(std::get<Is>(std::forward<Tuple>(t))); }...
  );
}
template<typename Tuple, typename Functor>
void foreach_tuple( Tuple&& t, Functor&& f ) {
  foreach_tuple_helper( make_seq<std::tuple_size< typename std::decay<Tuple>::type >::value>(), std::forward<Tuple>(t), std::forward<Functor>(f) );
}

do_in_order 在我上次检查时在 clang 中无法使用,但是在您的编译器上工作的等效索引技巧应该很容易通过谷歌搜索获得。


我的“foo”需要传入2个参数。我为这个函数对象添加了一个构造函数,以便存储另一个参数,并将该参数手动插入到foreach_tuple_helper -> std::forward<Functor>(f)(myArgAsDataMemberInClosure, std::get(stuff...))中。虽然这样可以工作,但我不认为这是正确的处理方式,因为我必须增加“通用”功能本身,那么如何传递附加参数呢?我的调用代码如下:do_in_order(tuple, myFunctor(thatExtraArgument)); - ScarletAmaranth
@ScarletAmaranth 要么在 foo_functor 上使用 std::bind,绑定一个参数,留下另一个参数进行调用,要么编写自己的变体。do_in_order(tuple, std::bind( foo_functor, _1, fixed_argument ) ),或者类似这样的东西。(在拉取 using namespace std::placeholders 以获取 _1 的视图后) - Yakk - Adam Nevraumont

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接