4

Could someone explain why the following c++ code is not behaving as expected:

struct Object {   
  template< int i >
  void foo(){ } 
};

template<int counter>
struct Container {
  Object v[counter];

  void test(){
    // this works as expected
    Object a; a.foo<1>();

    // This works as well:
    Object *b = new Object(); b->foo<1>();

    // now try the same thing with the array:  
    v[0] = Object(); // that's fine (just testing access to the array)

# if defined BUG1
    v[0].foo<1>();   // compilation fails 
# elif defined BUG2
    (v[0]).foo<1>(); // compilation fails
# elif defined BUG3
    auto &o = v[0];
    o.foo<1>();      // compilation fails
# else
    Object &o = v[0];
    o.foo<1>();      // works
# endif
  }
};

int main(){
  Container<10> container;
}

The code above compiles fine without flag. If one of the flag BUG1 to BUG3 is set, the compilation fails with either GCC 4.6 or 4.7 and with clang 3.2 (which seems to indicate it is not a GCC bug).

Lines 21 to 29 are doing exactly the same thing semantically (ie calling a method of the first element of the Object array), but only the last version compiles. The problem only seems to arise when I try to call a templated method from a template object.

BUG1 is just the "normal" way of writing the call.

BUG2 is the same thing, but the array access is protected by parenthesis in case there was a precedence problem (but there shouldn't be any).

BUG3 shows that type inference is not working either (needs to be compiled with c++11 support).

The last version works fine, but I don't understand why using a temporary variable to store the reference solves the problem.

I am curious to know why the other three are not valid.

Thanks

Thibaut
  • 2,400
  • 1
  • 16
  • 28
  • 1
    Try `v[0].template foo<1>();`. – Kerrek SB Dec 01 '12 at 15:48
  • @KerrekSB: The interesting thing is why this is necessary. It seems that v[0] is not a dependent expression, so the compiler should be able to resolve its type while parsing the template. – Vaughn Cato Dec 01 '12 at 15:51
  • @VaughnCato: I haven't tested it and I'm not sure, either. It was just a suggestion... The OP is not telling us what the error is, after all, and there's only so much guessing one can do. – Kerrek SB Dec 01 '12 at 15:52
  • @KerrekSB: sorry, it wasn't meant as a criticism of your comment. – Vaughn Cato Dec 01 '12 at 15:53
  • I tried you sample with Visual Studio C++ 2008 Express. BUG1, BUG2, and BUG3 all compile with no error. – brian beuning Dec 01 '12 at 15:53
  • @VaughnCato: No, no, don't worry, I didn't take it as criticism. I just wanted to clarify that I wasn't claiming to have an answer. – Kerrek SB Dec 01 '12 at 15:54
  • @KerrekSB: it does work, although i never came across this syntax before. As VaughnCato said, it doesn't totally explain why the other ways to write it are wrong. – Thibaut Dec 01 '12 at 15:54
  • @Thibaut: The only explanation I can think of is that because `counter` is a template parameter, `v` is a dependent name. I'm not sure if that's actually the case, but that would explain why you need to say `template`. – Kerrek SB Dec 01 '12 at 15:56
  • 1
    Ah, looks like `v` is a *value-dependent* name (see 14.6.2). – Kerrek SB Dec 01 '12 at 15:57
  • @KerrekSB: the problem is not only v, if foo is not a templated method, the code compiles. If Container is not template but foo is, the code compiles as well. The problem only arises when they are both template. – Thibaut Dec 01 '12 at 16:00
  • @Thibaut: That's because the default-disambiguation of a dependent name is "value". Only *type names* and *template names* need to be explicitly disambiguated. For an ordinary function `foo`, the value is what you want. – Kerrek SB Dec 01 '12 at 16:01
  • @KerrekSB: thanks, it starts to make sense now. The only thing i don't understand now is why the type inference failed in the third case? – Thibaut Dec 01 '12 at 16:06
  • @Thibaut: I suppose `o` is also a dependent expression?! That's interesting, though... – Kerrek SB Dec 01 '12 at 16:09
  • 1
    @Thibaut: Yeah, I think that's actually a pretty easy argument: With `auto`, the type is deduced, and it's deduced as a dependent type (namely the type of `Object[counter]()[0]`). On the other branch, you say `Object &` explicitly, so there's nothing deduced. If you changed the name of the `Object` type, the `auto` code would continue to work, while the explicit code would break. By being explicit you're saying that you know something about how the template is going to be used that the compiler doesn't. – Kerrek SB Dec 01 '12 at 16:15
  • Oh I see, I thought the type inference would match `Object` directly instead of `Object[counter]()[0]`, but your explanation makes sense, thanks. – Thibaut Dec 01 '12 at 16:18
  • Even though the actual type of v[0] doesn't change based on the template parameter, the rules of what makes a expression dependent aren't sophisticated enough to make it be considered non-dependent. – Vaughn Cato Dec 01 '12 at 16:19
  • @VaughnCato: it seems to be only a compiler issue then, or is the standard not strict enough to allow template independent type inference? – Thibaut Dec 01 '12 at 16:23
  • The C++03 standard is clear that `v[0]` is a dependent expression. I believe it would be reasonable for a later standard to have more sophisticated rules that make it be non-dependent though. – Vaughn Cato Dec 01 '12 at 16:32

2 Answers2

1

You have to use template as:

v[0].template foo<1>();  

auto &o = v[0];
o.template foo<1>();     

Because the declaration of v depends on the template argument, which makes v a dependent name.

Here the template keyword tells compiler that whatever follows is a template (in your case, foo is indeed a template). If foo is not a template, then the template keyword is not required (in fact, it would be an error).

The problem is that o.foo<1>() can be parsed/interpreted in two ways: one is just as you expect (a function call), the other way is this:

(o.foo) < 1  //partially parsed

that is, foo is a member data (not function), and you've comparing it with 1. So to tell the compiler that < is not used to compare o.foo with 1, rather it is used to pass template argument 1 to the function template, you're required to use template keyword.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • But then why is it working if foo is not a template method? The problem doesn't seem to come from v. – Thibaut Dec 01 '12 at 16:01
  • Thanks, very clear explanation. Any idea why type inference is failing as well in the third case? I thought the auto keyword would disambiguate in the same way as declaring the type manually. – Thibaut Dec 01 '12 at 16:12
  • @Thibaut: Think of `auto` as template parameter `T`. The value of these depends on context, and here you're working under *dependent* context: dependent on the template argument `counter`. – Nawaz Dec 01 '12 at 16:13
1

Inside templates, expressions can be type-dependent or value-dependent. From 14.6.2:

types and expressions may depend on the type and/or value of template parameters

In your case, counter is a template argument, and the declaration of v depends on it, making v[0] a value-dependent expression. Thus the name foo is a dependent-name which you must disambiguate as a template name by saying:

v[0].template foo<1>();
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Yes! The array size is value-dependent, making the array type be type-dependent, making the name `v` be value-dependent, making the expression `v[0]` be value-dependent. – Vaughn Cato Dec 01 '12 at 16:11