0

While experimenting with this stackoverflow answer I encountered a compilation error I don't understand.

With #if 1 the compilation fails with following error log whereas with if 0 the compilation is OK.

Full error log:

Output of x86-64 gcc 11.2 (Compiler #1)
<source>: In function 'void remove(std::vector<T>&, size_t)':
<source>:8:3: error: need 'typename' before 'std::vector<T>::iterator' because 'std::vector<T>' is a dependent scope
    8 |   std::vector<T>::iterator it = vec.begin();
      |   ^~~
<source>:8:27: error: expected ';' before 'it'
    8 |   std::vector<T>::iterator it = vec.begin();
      |                           ^~~
      |                           ;
<source>:9:16: error: 'it' was not declared in this scope; did you mean 'int'?
    9 |   std::advance(it, pos);
      |                ^~
      |                int
<source>: In instantiation of 'void remove(std::vector<T>&, size_t) [with T = int; size_t = long unsigned int]':
<source>:25:9:   required from here
<source>:8:19: error: dependent-name 'std::vector<T>::iterator' is parsed as a non-type, but instantiation yields a type
    8 |   std::vector<T>::iterator it = vec.begin();
      |                   ^~~~~~~~
<source>:8:19: note: say 'typename std::vector<T>::iterator' if a type is meant

Code (available here):

#include <iostream>
#include <vector>

#if 1
template <typename T>
void remove(std::vector<T>& vec, size_t pos)
{
  std::vector<T>::iterator it = vec.begin();
  std::advance(it, pos);
  vec.erase(it);
}
#else

template <typename T>
void remove(std::vector<T>& vec, size_t pos)
{
  vec.erase(vec.begin() + pos); 
}
#endif

int main()
{
  std::vector<int> myvector{ 1,2,3,4 };

  remove(myvector, 2);

  for (auto element : myvector)
    std::cout << ' ' << element;

  std::cout << '\n';
  return 0;
}

Now if I do what the compiler suggests (typename std::vector<T>::iterator it = vec.begin();) it compiles, but I don't really understand why typename is required here.

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
  • 2
    I'm not sure I understand the question. The compiler says that `std::vector::iterator` is a dependent type, so it requires `typename` before the declaration. What exactly is the "strange" part you refer to? – UnholySheep Jan 14 '22 at 10:51
  • @UnholySheep "strange" is not exactly the right word, I just don't understand the error message. Editing... – Jabberwocky Jan 14 '22 at 10:55

1 Answers1

2

The error message says it all:

error: dependent-name 'std::vector<T>::iterator' is parsed as a non-type, but instantiation yields a type

I.e., whilst for you as a programmer it is apparent that std::vector<T>::iterator is a type, for the compiler it is not, and the lack of a leading typename means it parses the dependent-name iterator as a non-type, but when instantiating the function template and thus its blueprinted definition for T as int, std::vector<T>::iterator is resolved as the (member alias declaration) type std::vector<int>::iterator.

Whilst P0634R3 (Down with typename!), introduced for C++20:

[...] removes the need to disambiguate a dependent name as a typename via the typename keyword from several places where this is already unambiguous

the example above is not such a place/context. To understand why the compiler cannot resolve this unambiguously for all T's, see the example in the end of this answer.

If anything this is a compilation error resulting from a verbose approach to the function's definition. There is no need to include a dependent name in the declaration of the iterator variable:

void remove(std::vector<T>& vec, size_t pos)
{
  auto it = vec.begin();
  std::advance(it, pos);
  vec.erase(it);
}

template<typename T>
struct Evil {
    using iterator = T*;  
};

template<>
struct Evil<int> {
    static constexpr int iterator{42};
};

template<typename T>
void f() {
     static_cast<void>(Evil<T>::iterator);   
}

int main() {
    f<int>();   // well-formed by evil explicit specialization
    f<char>();  // ill-formed by primary template where `iterator` is a type
                // error: missing 'typename' prior to dependent type name 'Evil<char>::iterator'
}

Or, courtesy of @Jarod42, even more evil where each case might work but do entirely different things.

template <bool isType>
struct Evil {
    using var = int;
};

template<>
struct Evil<false> {
    static constexpr int var {42};
};

template<bool isType>
void f()
{
    [[maybe_unused]]int b = 42;
    if constexpr (isType)
    {
        [[maybe_unused]] typename
        Evil<isType>::var * b; // is it a pointer declaration shadowing b
                               // or a multiplication
    } else {
        Evil<isType>::var * b; // is it a pointer declaration shadowing b
                               // or a multiplication
    }
}

int main()
{
    f<true>();
    f<false>();
}

DEMO.

dfrib
  • 70,367
  • 12
  • 127
  • 192