Consider this code:

struct B {}; struct D1 : B {}; struct D2 : B {}; #define get if (s) return d1; else return d2; volatile bool s; struct C { const B& f() const { get } B& f() { get } private: D1 d1; D2 d2; }; 

Here it can be seen that different versions of f() should return the same object (in one case - constant, in the other - not), based on some selection logic, which can be quite complex. In the example, macro substitution via #define used to eliminate duplication of the code of this logic.

Is it possible to avoid duplication of code in different versions of f() without resorting to the services of a preprocessor?

    4 answers 4

    The problem of code duplication due to constancy considerations usually occurs in two variants:

    1. At the level of individual functions: when there are two functions with the same implementation, differing only in the constancy of the input types (including, as a special case, the constancy of *this in the class method) and the corresponding constancy of the return value.

      In the C language, one of the known idioms for solving this problem is to write a single function that solves the problem in terms of keeping the input data constant, and then simply unconditionally removes the constancy from the return value (see, for example, the standard function strstr ). In this case, it is assumed that the calling code, being aware of the situation with data constancy, "gentlemanly" will return the "constance" lost during the function call into place.

      In C ++, this approach is also technically applicable, but it is not customary to use it. More precisely, the traditional C ++ idiom, which is based internally on the same approach, is externally implemented with a slight difference: a full implementation is provided for the constant version of the function, and the second one is built on it - the non-constant version of the same function. The latter is implemented through a constant by removing constancy from the return value.

       const return_type *foo(const input_type *argument) { ... } return_type *foo(input_type *argument) { return const_cast<return_type *>(foo(const_cast<const input_type *>(argument)); } 

      Exactly this approach is perfect in your case.

    2. At the level of individual classes: the code needs to implement two classes that are virtually identical in terms of the source code, and differ only in the external constancy of the data being processed. A good example: the constant and non-constant version of the container iterator class.

      In such a situation, one of the viable approaches is the implementation of the overall functionality in the form of a template class, parameterized by the necessary number of types (in the simplest case, one), and the implementation of the required final classes through the specializations of this template. Sort of

       template <typename T> class list { template <typename U> class iterator_impl { ... }; typedef iterator_impl<T> iterator; typedef iterator_impl<const T> const_iterator; ... }; 

    PS Your own answer using the template function is actually an adaptation of the above second approach to the first situation. It will undoubtedly work, but it is in such a situation that the banal version with const_cast seems to me that it looks simpler and more appropriate.

      You can write something like the following

       struct C { const B& f() const { if (s) return d1; else return d2; } B& f() { return const_cast<B &>( const_cast<const C *>( this )->f() ); } private: D1 d1; D2 d2; }; 

      Here is a demo program.

       #include <iostream> struct B {}; struct D1 : B {}; struct D2 : B {}; bool s; struct C { const B& f() const { std::cout << "const B & f() const" << std::endl; if (s) return d1; else return d2; } B& f() { std::cout << "B & f()" << std::endl; return const_cast<B &>( const_cast<const C *>( this )->f() ); } private: D1 d1; D2 d2; }; int main() { C c1; c1.f(); std::cout << std::endl; const C c2; c2.f(); return 0; } 

      Its output to the console

       B & f() const B & f() const const B & f() const 

        I came up with a variant with a template friendly function:

         template<class R, class T> R& g(T* t); struct C { const B& f() const { return g<const B&>(this); } B& f() { return g<B&>(this); } private: D1 d1; D2 d2; template<class R, class T> friend R& g(T* t); }; template<class R, class T> R& g(T* t) { if (s) return t->d1; else return t->d2; } 

        Or you can transfer it to the class altogether:

         struct C { const B& f() const; B& f(); private: D1 d1; D2 d2; template<class R, class T> static R& g(T* t) { if (s) return t->d1; else return t->d2; } }; const B& C::f() const { return g<const B&>(this); } B& C::f() { return g<B&>(this); } 

        And since type R in fact be derived from the fact that there is constancy in T , then this type can be completely removed from the template:

         template<class T> static std::conditional_t<std::is_const_v<T>, const B&, B&> g(T* t) { if (s) return t->d1; else return t->d2; } 

        So the need to explicitly specify the type when calling g eliminated:

         const B& C::f() const { return g(this); } B& C::f() { return g(this); } 

          The obvious option:

           class A { bool flag; int a; int b; public: int& get() { return flag ? a : b; } const int& get() const { return ((A*)this)->get(); } }; 

          Example:

           int main() { A a {}; const A ca {}; static_assert(std::is_same<int&, decltype(a.get())>::value, "!!"); static_assert(std::is_same<const int&, decltype(ca.get())>::value, "!!"); } 
          • here is essentially the same const_cast , only now it is hidden behind the sishny ghost - mymedia
          • @mymedia for sure. Thought and confused with the reverse cast. Corrected the answer - int3
          • To call a constant function from a non-constant function is a normal practice. But when the opposite is true - you can grab UB from similar ghosts. - 伪位蔚蠂慰位蠀蟿