Home > database >  calling base class constructor using decltype (to get more out of CTAD) works in gcc and clang but n
calling base class constructor using decltype (to get more out of CTAD) works in gcc and clang but n

Time:01-04

I'm working on a hobby programming language that transpiles to C (still very alpha quality: https://github.com/ehren/cmile).

I'm working on inheritance and constructors currently. The C code below writes the derived classes in a way that's agnostic as to whether their base class is a template or not (this simplifies various things in my compiler).

Note that inheriting from a decltype works in all 3 compilers. But calling a base class constructor using decltype in the initializer list e.g. Child(const T& t) : decltype(Base(std::declval<T>())(t) {} only works with g and clang -std=c 20 but not msvc19 /std:c 20

Here's the complete example that fails in MSVC with "error C2059: syntax error: 'type'" but works in clang or gcc:

#include <utility>

template <typename T>
struct Generic {
    T x;
    explicit Generic(const T& x) : x(x) {};
};

struct Concrete {
    int x;
    explicit Concrete(int x) : x(x) {};
};

template <typename T>
struct GenericChild : decltype(Generic(std::declval<T>())) {
    // explicit GenericChild(const T& t) : Generic(t) {};      // rejected by all 3 compilers (very annoying that CTAD doesn't work here)
    // explicit GenericChild(const T& t) : Generic<T>(t) {};   // explicitly calling base constructor as a template works fine in all compilers (but requires more tracking in my transpiler)
    explicit GenericChild(const T& t) : decltype(Generic(std::declval<T>())) (t) {};   // my desired solution: why does this fail with MSVC only? 
};

template <typename T>
struct ConcreteChild : decltype(Concrete(std::declval<T>())) {
    // explicit ConcreteChild(const T& t) : Concrete(t) {};   // this is fine of course
    explicit ConcreteChild(const T& t) : decltype(Concrete(std::declval<T>())) (t) {};   // "base class is a template?" agnostic version. rejected by MSVC.
    // aside: ^ removing both constructors here ICEs MSVC
};


int main() {
    auto f = Generic(5);
    auto f2 = GenericChild(5);
    auto f3 = Concrete(5);
    auto f4 = ConcreteChild(5);
}

or link: https://godbolt.org/z/P7Wh99qT9

Which compiler is in the right to accept/reject this code?

Is there a change I can make so MSVC will accept the decltype in the initializer list? (without having to know whether the Base constructor must be called explicitly as Base<T>(t) or Base(t)). I suppose there are various tricks that can be employed to explicitly check if Base is a template (but I'm not sure how straightforwardly they'll apply to the initializer list base class constructor call case if the decltype approach doesn't work).

Just as an aside, the C above (part of it; also ignoring refcounting and other details) would look like this in my language. The need for a construcor call in the initializer list (in the transpiled C output) will be inferred from the presence of the super.init call in the transpiled language's constructor:

class (Generic:
    x 
)

class (GenericChild(Generic):
    def (init, x:
        super.init(x)
    )
)

def (main:
    f = Generic(5)
    f2 = GenericChild(5)
)

CodePudding user response:

Clang and GCC are right. [class.base.init]/3 says:

A mem-initializer-list can initialize a base class using any class-or-decltype that denotes that base class type.

I can't prove that there's not some other rule in there that makes your example illegal, but I strongly suspect that MSVC is just having trouble with parsing. Parsing ctor-initializers is really hard.

  • Related