the_mandrill
the_mandrill

Reputation: 30832

Can class-local types be used in template classes before they are defined

I have a situation where I've got a template class that really just exposes one public function, and uses a locally-defined struct used for internal state which I don't want cluttering up the public section of my header. For instance:

template <class T>
class MyClass {
public:

   struct InternalState {
       // lots of noisy stuff I don't want to see in the public section
   };

   void userCallableFunction() {
      InternalState state;
      doPrivateStuff(state);
   }

 private:
   doPrivateStuff(InternalState& state) {
      // ok for the internals to go here 
   }
 };

I want to push the definition of InternalState down into the private section as the client of the class doesn't need to see it. I want to reduce the noise so it looks like this:

template <class T>
class MyClass {
public:
   // only the stuff the client needs to see here
   void userCallableFunction() {
      InternalState state;
      doPrivateStuff(state);
   }

 private:
   // fine for the private noise to be down here
   struct InternalState {
   };

    ... 
 };

The question is: is it legal to do this? ie declare InternalState after it's used in userCallableFunc() ? I know that you can't do this for non-inlined/templated functions, but which rules apply for inline/template functions?

EDIT:

I've tried this in Visual Studio 2010, Clang 4.1 and gcc 4.8.1 (sample in IdeOne, and also a non-templated but inlined case) and it builds successfully for all. So the issue is, is it safe and portable to do this? Please provide references rather than just saying 'no you can't do this'.

Upvotes: 1

Views: 66

Answers (2)

dyp
dyp

Reputation: 39101

As an unqualified-id (no ::) and not being of a syntax that it could be interpreted as a function, where ADL could apply, the name InternalState in the statement

InternalState state;

is looked up using normal unqualified lookup (lookup for unqualifed names).

For unqualified names inside the body of a member function, a special rule applies, see [basic.lookup.unqual]/8

For the members of a class X, a name used in a member function body [...], shall be declared in one of the following ways:

  • before its use in the block in which it is used or in an enclosing block (6.3), or
  • shall be a member of class X or be a member of a base class of X (10.2), or

Note that this imposes an ordering on the unqualified lookup: First, the enclosing blocks are searched, then, if nothing has been found, the class members are searched. Also note: as InternalState in this context is not dependent on a template-parameter, the base class scope won't be searched (if there was a base class).

The important part is a bit subtle, unfortunately: shall be a member of class X does not imply shall be declared before the member function body. For example,

struct X
{
    int meow()
    {
        return m; // (A)
    }

    int m;
} // (B)
;

In the line (A), the name m cannot be found in the current block (or any enclosing blocks, blocks are compound-statements, not just any {}). Therefore, it is looked up in the whole set of members of X. This is only possible once X has been completed, i.e. at the point (B).

The same applies to names that refer to nested classes -- after all, name lookup needs to find out what kind (e.g. function, variable, type) of entity the name refers to. Also, lookup for non-elaborated names IIRC doesn't discriminate between these kinds.

Unfortunately, that's the best I can find right now :/ I'm not happy as it's quite subtle and only answers the question indirectly.

Upvotes: 1

Jens
Jens

Reputation: 9406

You can only forward declare InternalState and then use pointers or references to it before the compiler sees the definition:

template <class T>
class MyClass {
public:
   // only the stuff the client needs to see here
   void userCallableFunction() {
      std::unique_ptr<InternalState> state = createInternal();
      doPrivateStuff(*state);
   }

 private:
   void doPrivateStuff(InternalState const&);

   // fine for the private noise to be down here
   struct InternalState {
   };

   std::unique_ptr<InternalState> createInternal() {
       return std::make_unique<InternalState>();
   }

 };

It is similar to the PIMPL idiom, so you might want to look this up.

Upvotes: 0

Related Questions