user5244399
user5244399

Reputation:

Avoid literally duplicating code for const and non-const with auto keyword?

Okay, I did some research and apparently there are a lot of duplicate questions on SO on this topic, to name a few:

etc. But I cannot help but raise this again, because

  1. With auto-typed return value, I am literally duplicating the function body with the only difference being the const function qualifier.
  2. It is possible that const version and non-const version may return types that are totally incompatible from each other. In such cases, neither Scott Meyers's const_cast idiom, nor the "private const function returning non-const" technique would work.

As an example:

struct A {
    std::vector<int> examples;
    auto get() { return examples.begin(); }
    auto get() const { return examples.begin(); }
};  // Do I really have to duplicate?
    // My real-world code is much longer
    // and there are a lot of such get()'s

In this particular case, auto get() returns an iterator while auto get() const returns a const_iterator, and the two cannot be converted into each other (I know erase(it,it) trick does the conversion in this case but that's not the point). The const_cast hence does not work; even if it works, that requires the programmer to manually deduce auto type, totally defeating the purpose of using auto.

So is there really no way except with macros?

Upvotes: 7

Views: 2827

Answers (3)

ayurchen
ayurchen

Reputation: 416

struct A {
    std::vector<int> examples;

private:
    template <typename ThisType>
    static auto
    get_tmpl(ThisType& t) { return t.examples.begin(); }

public:
    auto get()       { return get_tmpl(*this); }
    auto get() const { return get_tmpl(*this); }
};

How about the above? Yes, you still need to declare both methods, but the logic can be contained in a single static template method. By adding a template parameter for return type this will work even for pre-C++11 code.

Upvotes: 2

Oliv
Oliv

Reputation: 18081

Just a hypothetical solution, that I am thinking about applying every where once we will have concepts: using friend free function in place of member function.

//Forwarding ref concept
template<class T,class U>
concept FRef = std::is_same_v<std::decay_t<T>,std::decay_t<U>>;

struct A{
  std::vector<int> examples;

  friend decltype(auto) get(FRef{T,A}&& aA){
    return std::forward<T>(aA).examples.begin();
      //the std::forward is actualy not necessary here 
      //because there are no overload value reference overload of begin.
    }
  };

Upvotes: 0

Carl
Carl

Reputation: 2067

Ok, so after a bit of tinkering I came up with the following two solutions that allow you to keep the auto return type and only implement the getter once. It uses the opposite cast of what Meyer's does.

C++ 11/14

This version simply returns both versions in the implemented function, either with cbegin() or if you don't have that for your type this should work as a replacement for cbegin(): return static_cast<const A&>(*this).examples.begin(); Basically cast to constant and use the normal begin() function to obtain the constant one.

// Return both, and grab the required one
struct A
{
private:
    // This function does the actual getter work, hiding the template details
    // from the public interface, and allowing the use of auto as a return type
    auto get_do_work()
    {
        // Your getter logic etc.
        // ...
        // ...

        // Return both versions, but have the other functions extract the required one
        return std::make_pair(examples.begin(), examples.cbegin());
    }

public:
    std::vector<int> examples{ 0, 1, 2, 3, 4, 5 };

    // You'll get a regular iterator from the .first
    auto get()
    {
        return get_do_work().first;
    }

    // This will get a const iterator
    auto get() const
    {
        // Force using the non-const to get a const version here
        // Basically the inverse of Meyer's casting. Then just get
        // the const version of the type and return it
        return const_cast<A&>(*this).get_do_work().second;
    }

};

C++ 17 - Alternative with if constexpr

This one should be better since it only returns one value and it is known at compile time which value is obtained, so auto will know what to do. Otherwise the get() functions work mostly the same.

// With if constexpr
struct A
{
private:
    // This function does the actual getter work, hiding the template details
    // from the public interface, and allowing the use of auto as a return type
    template<bool asConst>
    auto get_do_work()
    {
        // Your getter logic etc.
        // ...
        // ...

        if constexpr (asConst)
        {
            return examples.cbegin();

            // Alternatively
            // return static_cast<const A&>(*this).examples.begin();
        }
        else
        {
            return examples.begin();
        }
    }

public:
    std::vector<int> examples{ 0, 1, 2, 3, 4, 5 };

    // Nothing special here, you'll get a regular iterator
    auto get()
    {
        return get_do_work<false>();
    }

    // This will get a const iterator
    auto get() const
    {
        // Force using the non-const to get a const version here
        // Basically the inverse of Meyer's casting, except you get a
        // const_iterator as a result, so no logical difference to users
        return const_cast<A&>(*this).get_do_work<true>();
    }
};

This may or may not work for your custom types, but it worked for me, and it solves the need for code duplication, although it uses a helper function. But in turn the actual getters become one-liners, so that should be reasonable.

The following main function was used to test both solutions, and worked as expected:

int main()
{    
    const A a;
    *a.get() += 1; // This is an error since it returns const_iterator

    A b;
    *b.get() += 1; // This works fine

    std::cout << *a.get() << "\n";
    std::cout << *b.get() << "\n";

    return 0;
}

Upvotes: 2

Related Questions