7

At work, I'm experimenting a bit to bring some reflection into our codebase. Basically what I want to achieve, is to capture a pointer to data-member inside the type of the data-member's initializer:

template<class Class, int Class::*dataMember>
struct Reflect
{
  operator int() {return 0;}
};

class Foo
{
public:
  int bar = Reflect<Foo, &Foo::bar>{};
};

Although clang 3.4.1 (http://gcc.godbolt.org/) and Intel C++ XE 14.0 are able to compile this piece of code, when using MSVC12 I get the following error message:

error C2065: 'bar' : undeclared identifier

error C2975: 'dataMember' : invalid template argument for 'Reflect', expected compile-time constant expression

Furthermore, gcc 4.9.2 also seems to have trouble with it: http://ideone.com/ZUVOMO.

So my questions are:

  1. Is the above piece of code valid C++11?
  2. If yes, are there any work arounds for the failing compilers?
Community
  • 1
  • 1

1 Answers1

2

What VC++ complains about is certainly not a problem; [basic.scope.pdecl]/1,6:

The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its initializer (if any), except as noted below.[…]

After the point of declaration of a class member, the member name can be looked up in the scope of its class.

This implies that the name lookup is fine. However, as pointed out by @hvd in the comments, there are certain ambiguities in the grammar of such constructs.
Presumably GCC parses the above line until the comma:

int bar = Reflect<Foo,
// at this point Reflect < Foo can be a perfectly fine relational-expression.
// stuff after the comma could be a declarator for a second member.

And bails out once the rest is encountered.


A workaround that makes GCC happy is
    int bar = decltype( Reflect<Foo, &Foo::bar>{} )();

Demo. This does not help with VC++ though, which apparently confuses the point of declaration as indicated by the error message. Thus moving the initializer into a constructor will work:

int bar;

Foo() : bar( Reflect<Foo, &Foo::bar>{} ) {}
// (also works for GCC)

... while providing an initializer at the declaration of bar cannot. Demo #2 on rextester.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • 1
    It's an unresolved language issue, IIRC. It's unclear when the comma separates template arguments, and when it separates two members. `int a=b::f;` could either declare member `a` and initialise it to `b::f`, or could declare member `a` initialised to `b::f`. It's complicated by the fact that NSDMIs may refer to not-yet-declared members of the class. –  Apr 01 '15 at 14:12
  • @hvd I assumed that the grammar covered this unambiguously. Good point. Can you find the DR associated with this? – Columbo Apr 01 '15 at 14:14
  • For the very related problem with default arguments for functions, there's [#325](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#325). Maybe that's what I was remembering, and maybe there isn't a separate issue for NSDMIs. –  Apr 01 '15 at 14:18
  • @hvd I'm fairly sure that `d=e` cannot be a valid template argument. However, this is a grammatical issue that starts in the parser, isn't it?` – Columbo Apr 01 '15 at 14:24
  • An overloaded assignment operator may be declared `constexpr`, so assignments can occur inside template arguments. –  Apr 01 '15 at 14:29
  • @hvd I can't [get it to work.](http://coliru.stacked-crooked.com/a/e236e8f365cfb3bf) – Columbo Apr 01 '15 at 14:34
  • @hvd I'm pretty sure that `a=b` is gramatically unable to be passed as a template-argument. If it is to designate some object, it must be an *id-expression*, or … - you know the rules in [temp.arg.nontype]/(1.3) – Columbo Apr 01 '15 at 14:36
  • `constexpr struct S { constexpr int operator=(S) const { return 1; } } a{}, b{}; template void f() { } int main() { f(); }` It doesn't designate an object, so those rules don't apply. –  Apr 01 '15 at 14:38
  • Actually, that is a syntax error and rejected as such by clang. It's a bug that it's accepted by GCC. `f<(a=b)>();` would be valid. The production *template-argument: constant-expression* grammatically simply doesn't allow a top-level assignment operator. In that case, perhaps there isn't any possibility for ambiguities. –  Apr 01 '15 at 14:45
  • @hvd Is (a=b) a conditional-expression then? – Columbo Apr 01 '15 at 14:48
  • @hvd Apparently that would be correct: (a=b) is a primary-expression of the form ( *expression* ), where expression can be an *assignment-expression*. – Columbo Apr 01 '15 at 14:51
  • Yes, a conditional-expression can be a primary-expression, and a primary-expression can be a parenthesised expression (which allows assignment). –  Apr 01 '15 at 14:51