2

The following code

#include <vector>
#include <string>

template<typename T>
struct V : public std::vector<T>
{
    using Impl = std::vector<T>;
    using typename Impl::vector; // the constructors
};

int main()
{
    std::string empty;
    V<std::string> meow{42UL, empty};
}

Is compiled fine by GCC 8.2 (calls the size_t, string constructor). However, clang up to 14 rejects it with

<source>:14:20: error: no matching constructor for initialization of 'V<std::string>' (aka 'V<basic_string<char>>')
    V<std::string> meow{42UL, empty};
                   ^   ~~~~~~~~~~~~~
<source>:5:8: note: candidate constructor (the implicit copy constructor) not viable: requires 1 argument, but 2 were provided
struct V : public std::vector<T>
       ^
<source>:5:8: note: candidate constructor (the implicit move constructor) not viable: requires 1 argument, but 2 were provided
<source>:5:8: note: candidate constructor (the implicit default constructor) not viable: requires 0 arguments, but 2 were provided

as if V had no constructors: https://godbolt.org/z/M91zb6Pjr

Replacing using typename Impl::vector; with using Impl::Impl; makes clang accept the code. What is going on here?

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
Bulletmagnet
  • 5,665
  • 2
  • 26
  • 56
  • The constructors inheriting is done via `using B::B` inside of `A`'s class definition, where `A` is a derived class and `B` is its base class. Where did you find `using typename B::vector;` form from? – The Dreams Wind Jul 28 '22 at 23:08
  • @TheDreamsWind - Presumably they found it by being C++-savvy enough to know about the injected class name, in both regular classes and in class templates. – StoryTeller - Unslander Monica Jul 28 '22 at 23:08
  • Btw, if this entire exercise is to avoid repeating template arguments, then the injected class names in both `V` and `vector` obviates the need. It's just somewhat less obvious due to how dependent names behave in templates. `using V::vector::vector;` is standard and works in all compilers (well, that I tried on godbolt), without needing to specify all template arguments. – StoryTeller - Unslander Monica Jul 28 '22 at 23:13
  • I found the `using typename B::vector` in code written by a cow orker. Said code was never seen by clang until yesterday. – Bulletmagnet Jul 29 '22 at 06:16

1 Answers1

5

Since the recent resolution of CWG issue 2070 it is not possible anymore to use a dependent alias to inherit the constructor in a using declaration, except if repeating the name of the alias.

You must use the same identifier to name the base class as you are using to refer to the constructor (the last unqualified-id after the nested-name-specifier), for example:

using std::vector<T>::vector;

or making use of the lookup of the injected class name of the base class in V:

using V::vector::vector;

or by a special rule for using-declarators the name of the alias may also be repeated instead of using the injected class name if it is dependent:

using Impl::Impl;

(see https://eel.is/c++draft/basic#class.qual-1.2 and https://eel.is/c++draft/namespace.udecl#1.sentence-3)

If you write using typename Impl::vector; instead it is not inheriting a constructor, but instead vector is going to be interpreted as a type named by the injected class name in vector<T>, which then is imported as the name vector into the class scope.

This requirement is to avoid that the same using line will cause the constructor to be inherited sometimes and a type name to be imported other times, depending on the specialization of the template.

Basically, after the resolution you know that the using declaration with a dependent nested-name-specifier is inheriting a constructor if and only if it is ending in A::A or A::A for some name A (potentially with additional template argument lists, etc.).

It seems that Clang has always implemented it this way, although I think it wasn't really correct according to the standard before the resolution of the issue.

user17732522
  • 53,019
  • 2
  • 56
  • 105