2

I have some class Foo and a std::list<std::reference_wrapper<Foo>> and would like to iterate over its elements with a range-based for loop:

#include <list>
#include <functional>
#include <iostream>


class Foo {
public:
    Foo(int a) : a(a) {}
    int a;
};

int main() {
    std::list<Foo> ls = {{1},{2},{3},{4}};
    std::list<std::reference_wrapper<Foo>> refs(ls.begin(), std::next(ls.begin(),2));
    
    for(auto &foo : refs) {
        std::cout << foo.get().a << std::endl;
    }

    for(Foo &foo : refs) {
        std::cout << foo.a << std::endl;
    }

    return 0;
}

Notice the additional get() when catching with auto, as we deduce type std::reference_wrapper<Foo>, whereas in the second case foo is already implicitly converted to type Foo& as we explicitly catch with this type.

I was actually looking for a way to catch with auto but implicitly cast away the std::reference_wrapper implicitly in order to not have to bother with the get() method all the time in the for body, so I tried introducing a fitting concept and catching with this, i.e. I tried

//this is not legal code

template<typename T>
concept LikeFoo = requires (T t) {
    { t.a };
};

int main() {
    std::list<Foo> ls = {{1},{2},{3},{4}};
    std::list<std::reference_wrapper<Foo>> refs(ls.begin(), std::next(ls.begin(),2));

    for(LikeFoo auto &foo : refs) {
        std::cout << foo.a << std::endl;
    }
    return 0;
}

and hoped that it would work. clang however deduces the type of foo to std::reference_wrapper<Foo>, so that in fact below code will be correct:

//this compiles with clang, but not with gcc

template<typename T>
concept LikeFoo = requires (T t) {
    { t.a };
};

int main() {
    std::list<Foo> ls = {{1},{2},{3},{4}};
    std::list<std::reference_wrapper<Foo>> refs(ls.begin(), std::next(ls.begin(),2));

    for(LikeFoo auto &foo : refs) {
        std::cout << foo.get().a << std::endl;
    }
    return 0;
}

However, gcc completely refuses to accept the range-based for loop and complains deduced initializer does not satisfy placeholder constraints, as it tries to check LikeFoo<std::reference_wrapper<Foo>>, which of course evaluates to false, so with gcc one cannot even catch foo concept-restricted. Two questions arise:

  • Which of the compilers is correct? Should LikeFoo auto& foo : refs be valid?
  • Is there a way to auto-catch (possibly concept-restricted) foo : refs such that one can avoid having to write get() in the for-loop body?

You can find this example at the Compiler explorer.

4
  • 1
    auto will always deduce the actual type. In order to get a Foo& an implicit conversion is needed. A concepts is a constraint on what type is allowed to be deduced. It can't apply conversions. Commented Aug 1, 2021 at 12:24
  • yes, thats what i thought as well, but is there a way to do this in the initialisation expression of the range-for-loop? Commented Aug 1, 2021 at 12:59
  • 1
    You can wrap refs in something that will automatically get() each element for you. But for (auto& foo : unwrap_reference_wrapper(refs)) just looks like a more convoluted version of for (Foo& : refs). It could have it's place in generic code though. Commented Aug 1, 2021 at 13:07
  • Yes, I'm writing tons of generic code, so this would be very useful. But I'm not sure how I would do that, without having to write a whole new iterator class etc. Commented Aug 1, 2021 at 16:57

2 Answers 2

2

Which of the compilers is correct? Should LikeFoo auto& foo : refs be valid?

No. refs is a range of reference_wrapper<Foo>&, so foo deduces to a reference to reference_wrapper<Foo> - which does not have a member named a. A constrained variable declaration doesn't change how deduction works, it just effectively behaves like an extra static_assert.

Is there a way to auto-catch (possibly concept-restricted) foo : refs such that one can avoid having to write get() in the for-loop body?

Just by writing refs? No. But you can write a range adaptor to convert your range of reference_wrapper<T> to a range of T&. There is already such a thing in the standard library, transform:

for (auto &foo : refs | std::views::transform([](auto r) -> decltype(auto) { return r.get(); })) {

That's a mouthful, so we can make it its own named adaptor:

inline constexpr auto unwrap_ref = std::views::transform(
    []<typename T>(std::reference_wrapper<T> ref) -> T& { return ref; });

And then you can write either:

for (auto &foo : refs | unwrap_ref) { ... }
for (auto &foo : unwrap_ref(refs)) { ... }

Either way, foo here deduces to be a Foo.

With a little bit more work, you can write a range adaptor that unwraps reference_wrapper<T> but preserves any other reference type.

Sign up to request clarification or add additional context in comments.

2 Comments

by reading constexpr, do I interpret this correctly that get() is already applied there at runtime, so there will be no runtime difference compared to just for (auto &foo : refs) and then always getting the element? That would be great!
also, it appears to me that clang won't compile this currently
0

Here is a bare minimum working example for a wrapper that calls get when being dereferenced.

#include <list>
#include <functional>
#include <iostream>

template <typename T>
struct reference_wrapper_unpacker {
    struct iterator {
        typename T::iterator it;

        iterator& operator++() {
            it++;
            return *this;
        }

        iterator& operator--() {
            it--;
            return *this;
        }

        typename T::value_type::type& operator*() {
            return it->get();
        }

        bool operator!=(const iterator& other) const {
            return it != other.it;
        }
    };
    reference_wrapper_unpacker(T& container) : t(container) {}

    T& t;
    
    iterator begin() const {
        return {t.begin()};
    }

    iterator end() const {
        return {t.end()};
    }
};

class Foo {
public:
    Foo(int a) : a(a) {}
    int a;
};

int main() {
    std::list<Foo> ls = {{1},{2},{3},{4}};
    std::list<std::reference_wrapper<Foo>> refs(ls.begin(), std::next(ls.begin(),2));
    
    for(auto &foo : refs) {
        std::cout << foo.get().a << std::endl;
    }

    for(Foo &foo : refs) {
        std::cout << foo.a << std::endl;
    }

    for(auto &foo : reference_wrapper_unpacker{refs}) {
        std::cout << foo.a << std::endl;
    }

    return 0;
}

To make it usable in generic code you would need to SFINAE to detect if the container actually has a reference_wrapper, and if not, just return the original container.

I'll leave that part out since it was not a part of the original question.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.