Home > front end >  clang doesn't see base class constructors pulled in via typedef
clang doesn't see base class constructors pulled in via typedef

Time:07-29

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?

CodePudding user response:

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.

  • Related