12

I have a class with a template parameter, and I want to call a method of it. It looks something like this:

template <typename T>
class Foo {
public:
    void doSomething() {
        for (auto& t: ts) {
            t.doSomething();
        }
    }
private:
    std::vector<T> ts;
};

This works, but I want to make doSomething() const if T itself is const (it is assumed that T::doSomething() will be const too). I found a possible solution (based on this question), but I don't like it.

template <bool enabled = std::is_const<T>::value>
typename std::enable_if<enabled, void>::type
doSomething() const {
    for (auto& t: ts) {
        t.doSomething();
    }
}

template <bool enabled = !std::is_const<T>::value>
typename std::enable_if<enabled, void>::type
doSomething() {
    for (auto& t: ts) {
        t.doSomething();
    }
}

It works fine, but it has a code duplication. Is there any way to avoid it?

Community
  • 1
  • 1
petersohn
  • 11,292
  • 13
  • 61
  • 98
  • As a workarround you could add `private: attribute(always_inline) _doSomething` and call that from your `(const) dosomething` – ted May 19 '15 at 19:13

1 Answers1

3

While not perfect here is a workaround: we have a non const member _doSomething() where we have the code that is the same for const and non const, except the function called on the underlying object. Since this member is non const we have to const_cast this to call it from a const Foo.

Since the code inside _doSomething is const safe, it is safe to (const_)cast const away.

Your code also will not compile for const, since you can not have a vector of const. A vector's elements must be assignable, which const types are typically not (they really should not, however: https://stackoverflow.com/a/17313104/258418).
You might want to consider std::vector<T*> rather than std::vector<T>. (I.e. store pointers rather than the objects in the vector)

#include <iostream>
#include <vector>

using namespace std;

class Bar {
public:
    Bar() {}
    void doSomething() const {
        std::cout << "const" << endl;
    }

    void doSomething() {
        std::cout << "NON const" << endl;
    }
};


template <typename T>
class Foo {
    void _doSomething() {
        /*for (auto& t: ts) {
            t.doSomething();
        }*/
        test.doSomething();
    }
public:
    Foo()/*T element) : ts({element})*/ {}

    template <bool enabled = std::is_const<T>::value>
    typename std::enable_if<enabled, void>::type
    doSomething() const {
        const_cast<typename std::remove_const<Foo<T>>::type*>(this)->_doSomething();
    }

    template <bool enabled = !std::is_const<T>::value>
    typename std::enable_if<enabled, void>::type
    doSomething() {
        _doSomething();
    }
private:
    //std::vector<T> ts; https://stackoverflow.com/a/17313104/258418
    T test;
};

int main()
{
    Foo<Bar> nonConstInst;
    Foo<const Bar> ConstInst;

    nonConstInst.doSomething();
    ConstInst.doSomething();
    return 0;
}
Community
  • 1
  • 1
ted
  • 4,791
  • 5
  • 38
  • 84
  • 1
    True, it doesn't work for vector. In reality I'm using `shared_ptr` there (`unique_ptr` would be better), so the const function would always work. Still, the contained objects are part of the state of the object, so I want `doSomething()` to be non-const if I am changing the state of the contained objects. But anyway your solution works and I didn't even have to `const_cast`. – petersohn May 20 '15 at 07:20
  • • Isn't `typename std::remove_const>::type*` just `Foo*`? What's the difference? (unless you want to do `typename std::remove_const::type*` to be generic, but I don't see the point) – user202729 Jan 19 '22 at 15:48
  • Remark: const_cast a originally const object is actually safe as long as no attempt is made to modify it. It's definitely dangerous however. [c++ - Is it allowed to cast away const on a const-defined object as long as it is not actually modified? - Stack Overflow](https://stackoverflow.com/questions/54504247/is-it-allowed-to-cast-away-const-on-a-const-defined-object-as-long-as-it-is-not) – user202729 Jan 19 '22 at 16:15