C++涵盖 const 和非常量方法的模板

发布于 2024-12-10 14:03:52 字数 1435 浏览 2 评论 0原文

我遇到了 const 和非 const 版本重复相同代码的问题。我可以用一些代码来说明这个问题。这里有两个示例访问者,一种修改访问的对象,另一种则不修改。

struct VisitorRead 
{
    template <class T>
    void operator()(T &t) { std::cin >> t; }
};

struct VisitorWrite 
{
    template <class T> 
    void operator()(const T &t) { std::cout << t << "\n"; }
};

现在这里是一个聚合对象 - 它只有两个数据成员,但我的实际代码要复杂得多:

struct Aggregate
{
    int i;
    double d;

    template <class Visitor>
    void operator()(Visitor &v)
    {
        v(i);
        v(d);
    }
    template <class Visitor>
    void operator()(Visitor &v) const
    {
        v(i);
        v(d);
    }
};

还有一个演示上述内容的函数:

static void test()
{
    Aggregate a;
    a(VisitorRead());
    const Aggregate b(a);
    b(VisitorWrite());
}

现在,这里的问题是 Aggregate::operator() 的重复code> 用于 const 和非 const 版本。

是否可以通过某种方式避免重复这段代码?

我有一个解决方案:

template <class Visitor, class Struct>
void visit(Visitor &v, Struct &s) 
{
    v(s.i);
    v(s.i);
}

static void test2()
{
    Aggregate a;
    visit(VisitorRead(), a);
    const Aggregate b(a);
    visit(VisitorWrite(), b);
}

这意味着不需要 Aggregate::operator() ,并且没有重复。但我对 visit() 是通用的而没有提及 Aggregate 类型这一事实感到不满意。

有更好的办法吗?

I have a problem with duplication of identical code for const and non-const versions. I can illustrate the problem with some code. Here are two sample visitors, one which modifies the visited objects and one which does not.

struct VisitorRead 
{
    template <class T>
    void operator()(T &t) { std::cin >> t; }
};

struct VisitorWrite 
{
    template <class T> 
    void operator()(const T &t) { std::cout << t << "\n"; }
};

Now here is an aggregate object - this has just two data members but my actual code is much more complex:

struct Aggregate
{
    int i;
    double d;

    template <class Visitor>
    void operator()(Visitor &v)
    {
        v(i);
        v(d);
    }
    template <class Visitor>
    void operator()(Visitor &v) const
    {
        v(i);
        v(d);
    }
};

And a function to demonstrate the above:

static void test()
{
    Aggregate a;
    a(VisitorRead());
    const Aggregate b(a);
    b(VisitorWrite());
}

Now, the problem here is the duplication of Aggregate::operator() for const and non-const versions.

Is it somehow possible to avoid duplication of this code?

I have one solution which is this:

template <class Visitor, class Struct>
void visit(Visitor &v, Struct &s) 
{
    v(s.i);
    v(s.i);
}

static void test2()
{
    Aggregate a;
    visit(VisitorRead(), a);
    const Aggregate b(a);
    visit(VisitorWrite(), b);
}

This means neither Aggregate::operator() is needed and there is no duplication. But I am not comfortable with the fact that visit() is generic with no mention of type Aggregate.

Is there a better way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

蒗幽 2024-12-17 14:03:52

我倾向于喜欢简单的解决方案,因此我会选择自由函数方法,可能会添加 SFINAE 来禁用除 Aggregate 之外的类型的函数:

template <typename Visitor, typename T>
typename std::enable_if< std::is_same<Aggregate,
                                   typename std::remove_const<T>::type 
                                  >::value
                       >::type
visit( Visitor & v, T & s ) {  // T can only be Aggregate or Aggregate const
    v(s.i);
    v(s.d);   
}

其中 enable_if,is_same 和 remove_const 实际上很容易实现

编辑: 写作时SFINAE 方法 我意识到在 OP 中提供普通模板化(无 SFINAE)解决方案存在相当多的问题,其中包括如果您需要提供多个可访问类型,则不同的模板会发生冲突(即它们与其他模板一样匹配)。通过提供 SFINAE,您实际上只为满足条件的类型提供 visit 函数,将奇怪的 SFINAE 转换为等价的:

// pseudocode, [] to mark *optional*
template <typename Visitor>
void visit( Visitor & v, Aggregate [const] & s ) {
   v( s.i );
   v( s.d );
}

I tend to like simple solutions, so I would go for the free-function approach, possibly adding SFINAE to disable the function for types other than Aggregate:

template <typename Visitor, typename T>
typename std::enable_if< std::is_same<Aggregate,
                                   typename std::remove_const<T>::type 
                                  >::value
                       >::type
visit( Visitor & v, T & s ) {  // T can only be Aggregate or Aggregate const
    v(s.i);
    v(s.d);   
}

Where enable_if, is_same and remove_const are actually simple to implement if you don't have a C++0x enabled compiler (or you can borrow them from boost type_traits)

EDIT: While writing the SFINAE approach I realized that there are quite a few problems in providing the plain templated (no SFINAE) solution in the OP, which include the fact that if you need to provide more than one visitable types, the different templates would collide (i.e. they would be as good a match as the others). By providing SFINAE you are actually providing the visit function only for the types that fulfill the condition, transforming the weird SFINAE into an equivalent to:

// pseudocode, [] to mark *optional*
template <typename Visitor>
void visit( Visitor & v, Aggregate [const] & s ) {
   v( s.i );
   v( s.d );
}
沙沙粒小 2024-12-17 14:03:52
struct Aggregate
{
    int i;
    double d;

    template <class Visitor>
    void operator()(Visitor &v)
    {
        visit(this, v);
    }
    template <class Visitor>
    void operator()(Visitor &v) const
    {
        visit(this, v);
    }
  private:
    template<typename ThisType, typename Visitor>
    static void visit(ThisType *self, Visitor &v) {
        v(self->i);
        v(self->d);
    }
};

好的,仍然有一些样板文件,但没有依赖于聚合实际成员的代码重复。与(例如)Scott Meyers 提倡的避免 getter 重复的 const_cast 方法不同,编译器将确保两个公共函数的 const 正确性。

struct Aggregate
{
    int i;
    double d;

    template <class Visitor>
    void operator()(Visitor &v)
    {
        visit(this, v);
    }
    template <class Visitor>
    void operator()(Visitor &v) const
    {
        visit(this, v);
    }
  private:
    template<typename ThisType, typename Visitor>
    static void visit(ThisType *self, Visitor &v) {
        v(self->i);
        v(self->d);
    }
};

OK, so there's still some boilerplate, but no duplication of the code that depends on the actual members of the Aggregate. And unlike the const_cast approach advocated by (e.g.) Scott Meyers to avoid duplication in getters, the compiler will ensure the const-correctness of both public functions.

时光清浅 2024-12-17 14:03:52

由于您的最终实现并不总是相同,因此我认为对于您所感知的“问题”没有真正的解决方案。

让我们想一想。我们必须满足Aggregate 为const 或非const 的情况。当然我们不应该放松这一点(例如仅提供非常量版本)。

现在,运算符的 const 版本只能调用通过 const-ref (或按值)获取参数的访问者,而非常量版本可以调用任何访问者。

您可能认为可以用两个实现之一替换另一个实现。为此,您始终会根据非常量版本来实现 const 版本,而不是相反。假设:

void operator()(Visitor & v) { /* #1, real work */ }

void operator()(Visitor & v) const
{
  const_cast<Aggregate *>(this)->operator()(v);  // #2, delegate
}

但为了使这一点有意义,第 2 行要求该操作在逻辑上是不可变的。例如,在典型的成员访问运算符中,这是可能的,您可以在其中提供对某些元素的常量或非常量引用。但在您的情况下,您无法保证 operator()(v) 调用在 *this 上不会发生变化!

因此,您的两个功能确实相当不同,尽管它们在形式上看起来很相似。你不能用一个来表达另一个。

也许您可以从另一种角度看待这一点:您的两个功能实际上并不相同。在伪代码中,它们是:

void operator()(Visitor & v) {
  v( (Aggregate *)->i );
  v( (Aggregate *)->d );
}

void operator()(Visitor & v) const {
  v( (const Aggregate *)->i );
  v( (const Aggregate *)->d );
}

实际上,想想看,也许如果您愿意稍微修改一下签名,就可以做一些事情:

template <bool C = false>
void visit(Visitor & v)
{
  typedef typename std::conditional<C, const Aggregate *, Aggregate *>::type this_p;
  v(const_cast<this_p>(this)->i);
  v(const_cast<this_p>(this)->d);
}

void operator()(Visitor & v) { visit<>(v); }
void operator()(Visitor & v) const { const_cast<Aggregate *>(this)->visit<true>()(v); }

Since your ultimate implementations are not always identical, I don't think there's a real solution for your perceived "problem".

Let's think about this. We have to cater for the situations where Aggregate is either const or non-const. Surely we should not relax that (e.g. by providing only a non-const version).

Now, the const-version of the operator can only call visitors which take their argument by const-ref (or by value), while the non-constant version can call any visitor.

You might think that you can replace one of the two implementations by the other. To do so, you would always implement the const version in terms of the non-const one, never the other way around. Hypothetically:

void operator()(Visitor & v) { /* #1, real work */ }

void operator()(Visitor & v) const
{
  const_cast<Aggregate *>(this)->operator()(v);  // #2, delegate
}

But for this to make sense, line #2 requires that the operation is logically non-mutating. This is possible for example in the typical member-access operator, where you provide either a constant or a non-constant reference to some element. But in your situation, you cannot guarantee that the operator()(v) call is non-mutating on *this!

Therefore, your two functions are really rather different, even though they look formally similar. You cannot express one in terms of the other.

Maybe you can see this another way: Your two functions aren't actually the same. In pseudo-code, they are:

void operator()(Visitor & v) {
  v( (Aggregate *)->i );
  v( (Aggregate *)->d );
}

void operator()(Visitor & v) const {
  v( (const Aggregate *)->i );
  v( (const Aggregate *)->d );
}

Actually, coming to think of it, perhaps if you're willing to modify the signature a bit, something can be done:

template <bool C = false>
void visit(Visitor & v)
{
  typedef typename std::conditional<C, const Aggregate *, Aggregate *>::type this_p;
  v(const_cast<this_p>(this)->i);
  v(const_cast<this_p>(this)->d);
}

void operator()(Visitor & v) { visit<>(v); }
void operator()(Visitor & v) const { const_cast<Aggregate *>(this)->visit<true>()(v); }
红墙和绿瓦 2024-12-17 14:03:52

通常对于这种类型的事情,最好使用有意义的方法。例如,load()save()。他们说了一些关于将通过访问者执行的操作的具体内容。通常会提供 const 和非 const 版本(无论如何,对于访问器之类的东西),因此它看起来只是重复,但可以在以后为您节省一些令人头痛的调试。如果您确实想要一个解决方法(我不建议这样做),那就声明方法const,并将所有成员mutable声明。

Normally with this type of thing, it's possibly better to use methods that make sense. For example, load() and save(). They say something specific about the operation that is to be carried out via the visitor. Typically both a const and non-const version is provided (for things like accessors anyway), so it only appears to be duplication, but could save you some headache debugging later down the line. If you really wanted a workaround (which I wouldn't advice), is to declare the method const, and all the members mutable.

可爱咩 2024-12-17 14:03:52

添加访问者特征来判断它是否正在修改(常量或非常量使用)。
这是由 STL 迭代器使用的。

Add visitor trait to tell whether it's modifying or not (const or non-const use).
This is used by STL iterators.

陌生 2024-12-17 14:03:52

您可以使用 const_cast 并更改 VisitorRead 的方法签名,以便它也采用 const T&作为参数,但我认为这是一个丑陋的解决方案。

You could use const_cast and change VisitorRead's method signature so it also take's const T& as a parameter, but I think that is an ugly solution.

风和你 2024-12-17 14:03:52

另一个解决方案 - 要求 Visitor 类具有一个在应用时添加 const 的元函数:

template <class Visitor>
static void visit(Visitor &v, typename Visitor::ApplyConst<Aggregate>::Type &a)
{
    v(a.i);
    v(a.d);
}

Another solution - require the Visitor class to have a metafunction that adds const when it applies:

template <class Visitor>
static void visit(Visitor &v, typename Visitor::ApplyConst<Aggregate>::Type &a)
{
    v(a.i);
    v(a.d);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文