C++ template to cover const and non-const method

后端 未结 7 1398
北荒
北荒 2020-12-29 22:48

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 s

相关标签:
7条回答
  • 2020-12-29 23:18

    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); }
    
    0 讨论(0)
  • 2020-12-29 23:20
    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.

    0 讨论(0)
  • 2020-12-29 23:21

    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.

    0 讨论(0)
  • 2020-12-29 23:23

    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);
    }
    
    0 讨论(0)
  • 2020-12-29 23:24

    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 );
    }
    
    0 讨论(0)
  • 2020-12-29 23:25

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

    0 讨论(0)
提交回复
热议问题