模板构造函数不由派生类继承

Template constructor is not inherited by derived class

提问人:felps321 提问时间:8/24/2023 更新时间:8/25/2023 访问量:122

问:

出于某种原因,Clang 不接受以下看似正确的代码,而 GCC 接受:

#include <cstdio>
#include <utility>
#include <concepts>

template <typename T>
struct Wrapper {
    T val;
};


template <typename T>
struct Base {
    template <typename U> requires std::is_same_v<U, Wrapper<T>>
    Base(U&&) { puts("Base constructor called"); }
};


template <typename T>
struct S : Base<T> {
    using B = Base<T>;

    using B::B;

    template <typename U> requires std::is_constructible_v<T, U>
    S(U&& v)
        : B( Wrapper<T>{ T(v) } ) { puts("S constructor called"); }
};


int main()
{
    // calls S constructor as expected
    S<int> a(10);

    // with GCC, calls Base constructor as expected,
    // Clang fails to compile this
    S<int> b(Wrapper<int>{1});
}

此代码使用 C++20 概念语法,但如果我使用 SFINAE,结果相同,并且我尝试了不同形式的 sfinae(两者和作为最后一个模板参数),结果是相同的。typename = std::enable_if_t<...>std::enable_if_t<..., bool> = true

有趣的是,当我从构造函数参数或(派生类)构造函数参数中删除转发引用 () 时,代码也会使用 Clang 编译。或者类似地,如果我向任一构造函数添加带有默认值的参数,问题就会消失。 看起来 Clang 认为这两个构造函数具有相同的“签名”,并丢弃了基类中的一个,以下是 cppreference 关于 Using-declaration 和继承构造函数的摘录:&&BaseS

As with using-declarations for any other non-static member functions,
if an inherited constructor matches the signature of one of the
constructors of Derived, it is hidden from lookup by the version
found in Derived.

但是,如果我采用基类构造函数并将其移动到它编译的派生类中,这意味着它们不匹配,否则我会得到重新声明错误。 这是 Clang 中的错误还是我误解了什么?

来自 clang 的错误如下:

foobar.cpp:37:12: error: no matching constructor for initialization of 'S<int>'
    S<int> b(Wrapper<int>{1});
           ^ ~~~~~~~~~~~~~~~
foobar.cpp:19:8: note: candidate constructor (the implicit copy constructor) not viable: no known con
version from 'Wrapper<int>' to 'const S<int>' for 1st argument
struct S : Base<T> {
       ^
foobar.cpp:19:8: note: candidate constructor (the implicit move constructor) not viable: no known con
version from 'Wrapper<int>' to 'S<int>' for 1st argument
struct S : Base<T> {
       ^
foobar.cpp:25:5: note: candidate template ignored: constraints not satisfied [with U = Wrapper<int>]
    S(U&& v)
    ^
foobar.cpp:24:36: note: because 'std::is_constructible_v<int, Wrapper<int> >' evaluated to false
    template <typename U> requires std::is_constructible_v<T, U>
                                   ^
1 error generated.
C++ C++11 模板

评论

4赞 Brian61354270 8/24/2023
Clang 16.0.0 和 trunk 接受它:godbolt.org/z/r6v3KxdPs。可能是最近修补的错误?
0赞 felps321 8/24/2023
@Brian61354270 有趣的是,我在 Clang 15.0.7 上,我以为这是最新版本,因为我使用的是滚动发布的 Linux 发行版

答:

0赞 felps321 8/25/2023 #1

这原来是 Clang 中的一个错误:https://github.com/llvm/llvm-project/issues/50886

如果有人也偶然发现了这个问题,以下是我如何解决它:

#include <cstdio>
#include <utility>
#include <concepts>

template <typename T>
struct Wrapper {
    T val;
};


template <typename T>
struct Base {
    template <typename U> requires std::is_same_v<U, Wrapper<T>>
    Base(U&&) { puts("Base constructor called"); }
};


template <typename T>
struct S : Base<T> {
    using B = Base<T>;

    using B::B;

    template <typename U> requires std::is_constructible_v<T, U>
    S(U&& v)
        : B( Wrapper<T>{ T(v) } ) { puts("S constructor called"); }
    

#if defined(__clang__) && __clang_major__ < 16
    template <typename U> S(U&& v) : B(std::forward<U>(v)) {}
#endif


};


int main()
{
    // calls S constructor as expected
    S<int> a(10);

    // calls Base constructor as expected
    S<int> b(Wrapper<int>{1});
}