5

Attempting to modify the code from this page.

Here's the problem code:

#include <iostream>
#include <array>

template<class T>
class const_reverse_wrapper
{
public:
    const_reverse_wrapper (const T& cont)
        : container_(cont)
    {
    }

    decltype( container_.rbegin() ) begin() const
    {
        return container_.rbegin();
    }

    decltype( container_.rend() ) end()
    {
        return container_.rend();
    }

private:
    const T & container_;
};

template<class T>
class reverse_wrapper
{
public:
    reverse_wrapper (T & cont)
        : container_(cont)
    {
    }

    decltype( container_.rbegin() ) begin()
    {
        return container_.rbegin();
    }

    decltype( container_.rend() ) end()
    {
        return container_.rend();
    }
private:
    T & container_;
};

template<class T>
const_reverse_wrapper<T> reversed (const T & cont)
{
    return const_reverse_wrapper<T>(cont);
}

template<class T>
reverse_wrapper<T> reverse (T & cont)
{
    return reverse_wrapper<T>(cont);
}

int main (int argc, char * argv[])
{
    std::array<int,4> a = { 1, 2, 3, 4 };
    for (int i : a)
        std::cout << i;
    return 0;
}

When I compile it, I get these errors:

> g++ -std=c++0x test2.cpp
test2.cpp:13:15: error: 'container_' was not declared in this scope
test2.cpp:13:15: error: 'container_' was not declared in this scope
test2.cpp:18:15: error: 'container_' was not declared in this scope
test2.cpp:18:15: error: 'container_' was not declared in this scope
test2.cpp:36:15: error: 'container_' was not declared in this scope
test2.cpp:36:15: error: 'container_' was not declared in this scope
test2.cpp:41:15: error: 'container_' was not declared in this scope
test2.cpp:41:15: error: 'container_' was not declared in this scope

When I move the private sections before the public sections in each class, the errors go away.

template<class T>
class const_reverse_wrapper
{
private:                    // <-----
    const T & container_;   // <-----
public:
    const_reverse_wrapper (const T& cont)
        : container_(cont)
    {
    }

    decltype( container_.rbegin() ) begin() const
    {
        return container_.rbegin();
    }

    decltype( container_.rend() ) end()
    {
        return container_.rend();
    }
};

template<class T>
class reverse_wrapper
{
private:              // <-----
    T & container_;   // <-----
public:
    reverse_wrapper (T & cont)
        : container_(cont)
    {
    }

    decltype( container_.rbegin() ) begin()
    {
        return container_.rbegin();
    }

    decltype( container_.rend() ) end()
    {
        return container_.rend();
    }
};

I've tried compiling with MinGW GCC 4.6.2 and 4.7.0 and get the same results. Is this a bug, or is there something else going on?

user1594322
  • 2,008
  • 3
  • 19
  • 16

1 Answers1

5

You had the same problem before C++11:

struct X{
  Foo f(){ return 42; } // error: 'Foo' does not name a type

  typedef int Foo;
};

The reason for this is that only the body of a member function is treated as if it was defined out-of-class with regards to member availability.

§9.2 [class.mem] p2

A class is considered a completely-defined object type (3.9) (or complete type) at the closing } of the class-specifier. Within the class member-specification, the class is regarded as complete within function bodies, default arguments, exception-specifications, and brace-or-equal-initializers for non-static data members (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification.

As such, only names previously seen inside of the class member-specification (as the standard calls it) can be used.

I see two possible fixes, one for your specific use-case and a general one. For your specific case, just use typename T::const_reverse_iterator. For the general case, use std::declval to obtain an object of a certain type for decltype and call the method on that:

#include <functional>

decltype(std::declval<T const&>().rbegin()) rbegin() const{ ... }
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • 1
    Thanks, does this mean member data (private or public) should be declared at the beginning of the class? I have always declared it at the end of the class and never had problems until now. – user1594322 Nov 05 '12 at 05:26
  • 1
    Ah, so the issue is decltype (which I had not used before), hence your typedef example. Now it's making sense. Thanks! – user1594322 Nov 05 '12 at 05:45
  • @user: Another good [example](http://liveworkspace.org/code/22d5e54911c673d728701cc206658540) would've been `sizeof` for C++03. – Xeo Nov 05 '12 at 05:49
  • Essentially `std::declval` is exploiting the availability of the complete class `T`(according to the quoted paragraph: ***Within the class member-specification, the class is regarded as complete within function bodies***). The problematic and previously unqualified member call, `rbegin` is now accessed via `declval`, rather then with the `T const&`. – damienh Nov 05 '12 at 16:46
  • @damienh: Ehh... not quite. I could aswell have written `T().crbegin()`, but what if `T` for whatever reason is not default-constructible? In generic code, you mostly have no idea how `T` is constructible, if at all through its constructor (maybe it can only be constructed through a factory function?). With `std::declval`, you just *have* a value of that type, without needing to know how you actually get one. Note that `std::declval` is only declared, not defined, and as such can only be used in unevaluated contexts like `decltype`. – Xeo Nov 05 '12 at 16:49
  • @Xeo: Thanks for clarifying this. So in `T().crbegin`, the subexpression `T()` presumes a default constructor exists - something that is not necessarily true. Whereas `std::declval()`, assumes nothing about `T`'s construction or how `T` instances are sourced. `std::declval` also allows value-semantics(via the dot-operator) within a `decltype` context. – damienh Nov 05 '12 at 17:19
  • @damienh: Correct... until that last sentence. "value-semantics" are completely unrelated to everything here. – Xeo Nov 05 '12 at 17:21