【问题标题】:Generic function that accepts const and non-const data and related function接受 const 和 non-const 数据的通用函数和相关函数
【发布时间】:2018-12-18 13:21:24
【问题描述】:

要使问题标题更有意义,请考虑以下代码:

template <typename T>
void write(const T& /*data*/)
{
    // use data as const
}

template <typename T>
void read(T& /*data*/)
{
    // modify data
}

struct S { 
    int a;
    int b;
    // a lot of primitive types here
};

void output(const S& s)
{
    write(s.a);
    write(s.b);
    // ...
}

void input(S& s)
{
    read(s.a);
    read(s.b);
    // ...
}

int main()
{
    S s;
    input(s);
    output(s);
}

我有 write 和 read 函数来操作原始数据,例如 int、double 等。我有几个结构类型,例如 S,其中包含很多原始数据成员,我需要输入并通过相应的函数input 和output 输出此类类型(不同类型有许多重载)。如您所见,output 和input 函数的内容基本相同,只是内部函数和类型常量不同。我想做一些通用(模板)函数use 来消除input/output 函数中的代码重复。

我认为模板签名如下(但我可能会遗漏一些东西):

template <typename F, typename T>
void use(T& t) { ... }

所以我可以像这样从input/output 调用use 函数:

use<write, S>(s); // in output function
use<read, S>(s);  // in input fuction

如何归档这种(或类似的)行为以消除代码重复?我正在使用 C++14。

【问题讨论】:

  • 不能只是use(T t) 并让T 成为T&amp; 或const T&amp; 适当吗?
  • 您认为use&lt;write, S&gt;(s)优于write(s)(甚至更明确的write&lt;S&gt;(s))的依据是什么?
  • @Peter:简单,想象一下结构的序列化/反序列化代码。您想列出成员一次,而不是两次。
  • FWIW,如果你想序列化一堆不同的类型,请查看 boost serialize:boost.org/doc/libs/1_68_0/libs/serialization/doc/index.html
  • @VittorioRomeo - 我明白这个问题是关于什么的。但是所寻求的解决方案仍然要求进行读写操作的语句是不同的。成员可能只列出一次,但它们用于更复杂的语句中,读取和写入必须不同。

标签: c++ templates c++14


【解决方案1】:
template <typename S, typename F>
void use(S&& s, F&& f)
{
    f(s.a);
    f(s.b);
    f(s.c);
}

用法:

use(s, [](const auto& x){ write(x); });
use(s, [](auto& x)      { read(x); });

live example on wandbox.org


如果您需要多种类型:

template <typename S, typename Target>
using EnableFor = std::enable_if_t<std::is_same_v<std::decay_t<S>, Target>>;

template <typename S, typename F>
auto use(S&& s, F&& f) -> EnableFor<S, S0>
{
    f(s.a);
    f(s.b);
    f(s.c);
}

template <typename S, typename F>
auto use(S&& s, F&& f) -> EnableFor<S, S1>
{
    f(s.d);
    f(s.e);
    f(s.f);
}

用法:

int main()
{
    S0 s0; 
    use(s0, [](const auto& x){ write(x); });
    use(s0, [](auto& x)      { read(x); });

    S1 s1;
    use(s1, [](const auto& x){ write(x); });
    use(s1, [](auto& x)      { read(x); });
}

live example on wandbox.org

【讨论】:

  • 我应该如何处理另一种类型,例如S1 { int x, int y; };?。 IE。没有a,b,c 字段。
  • 您为此编写了另一个 use 函数。您可以将S 限制为S0 或S1,以便它们过载。
  • 我觉得模板功能应该有专门化吧?
  • @älёxölüt 更好的解决方案将在未来通过反射出现,但现在这就是你所拥有的。
  • @älёxölüt:专业化不能很好地配合功能模板和完美的转发。我会使用 enable_if 或 C++20 中的概念的两个重载。
【解决方案2】:

您可以定义正在使用的模板的接口:

template<class M, class S>
void use(S &&s) {
    M::method(s.a);
    M::method(s.b);
};

然后实现它:

template <typename T>
void write(const T& /*data*/)
{
    // use data as const
}

struct writer {
    template <typename T>
    static void method(const T& t)
    {
        write(t);
    }
};

现在你可以使用它了:

use<writer>(s);

对于read,也是一样的:

struct reader {
    template <typename T>
    static void method(T& t)
    {
        read(t);
    }
};

use<reader>(s);

【讨论】:

    【解决方案3】:

    我相信我明白你想要什么,但这是不可能的。目前没有办法在 C++ 通用流中为所有类实现。需要访问诸如类具有的字段数量、它们的相对地址以及编译时间类型等信息。

    目前他们多年来一直致力于将其添加到 C++ 中,寻求 Reflection TS。据我所知,它还没有为 C++20 做好准备。

    但是,您可以组织代码,使每个读/写方法都易于实现。例如:

    class A : public B, public C
    {
       private:
       vector<int> m_v;
       int m_x;
    };
    

    然后为它实现写

    template<>
    void write(const A& a)
    {
         write((const B&)a);
         write((const C&)a);
         write(a.m_v);
         write(a.m_x);
    }
    

    而对于像std::vector&lt;T&gt; 这样的常见数据结构,通过write&lt;T&gt; 实现写入。这需要时间,但使其具有技术性并使用重定向可以节省大量代码和时间。

    (你需要将write设为好友函数,或成员函数以使其访问私有成员)

    【讨论】:

    • "人们需要访问类的字段数量、它们的相对地址以及编译时间类型等信息。" 从 C++17 开始就可以做到这一点.谷歌搜索“magic_get”。
    • 有趣,它确实可以解决一半的问题(仍然需要解决识别字段的确切位置的问题,因为对齐规则使它变得不平凡)。不幸的是,magic_get 在类派生自其他任何东西时都不起作用 - 仅适用于普通结构/类。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-20
    • 1970-01-01
    • 1970-01-01
    • 2016-11-07
    • 1970-01-01
    相关资源
    最近更新 更多