如何在使用嵌套模板时暴露继承的构造函数?

huangapple go评论85阅读模式
英文:

How to expose inherited constructors when using nested templating?

问题

以下是您提供的代码部分的翻译:

#include <string>
#include <variant>

class my_variant : public std::variant<int, std::string>
{
    using variant::variant;

public:
    std::string foo()
    {
        return "foo";
    }
};
template <typename TChr>
class my_variant : public std::variant<int, std::string>
{
    using variant::variant;

public:
    // Changed to templated return type, compiles fine
    std::basic_string<TChr> foo() 
    {
        return "foo";
    }
};
template <typename TChr>
class my_variant : // Changed to templated base class
    public std::variant<int, std::basic_string<TChr>> 
{
    // Now this line won't compile !!
    using variant::variant; 

public:
    std::basic_string<TChr> foo()
    {
        return "foo";
    }
};
template <typename TChr>
class my_variant :
    public std::variant<int, std::basic_string<TChr>>
{
    // Added templating here, still fails with the same error message.
    using variant<int, std::basic_string<TChr>>::variant; 

public:
    std::basic_string<TChr> foo()
    {
        return "";
    }
};

希望这些翻译对您有所帮助。如果您有任何其他问题,请随时提问。

英文:

Consider this simple class derived from std::variant:

#include &lt;string&gt;
#include &lt;variant&gt;

class my_variant : public std::variant&lt;int, std::string&gt;
{
    using variant::variant;
public:
    std::string foo()
    {
        return &quot;foo&quot;;
    }
};

Notice, I am deliberately adding using variant:variant so that constructors from the base class are exposed in the derived class. If you do not do this, instances of my_variant class will not work with operator= without a bunch of re-definitions.
This compiles just fine.


Now let's start turning it into a template step-by-step.

template &lt;typename TChr&gt;
class my_variant : public std::variant&lt;int, std::string&gt;
{
    using variant::variant;

public:
    // Changed to templated return type, compiles fine
    std::basic_string&lt;TChr&gt; foo() 
    {
        return &quot;foo&quot;;
    }
};

Here, the only change is that we make use of the template parameter in the foo() method. Everything still compiles fine.

And now this:

template &lt;typename TChr&gt;
class my_variant : // Changed to templated base class
    public std::variant&lt;int, std::basic_string&lt;TChr&gt;&gt; 
{
    // Now this line won&#39;t compile !!
    using variant::variant; 

public:
    std::basic_string&lt;TChr&gt; foo()
    {
        return &quot;foo&quot;;
    }
};

As soon as I make use of the template parameter to describe the base class, I am getting the following compiler error:

&#39;variant&#39;: is not a class or namespace name 

for this line: using variant::variant;

I do not fully understand why this specific change causes a problem. So far I was thinking in the direction that maybe using variant::variant without specifying its template signature is a problem, so I tried this:

template &lt;typename TChr&gt;
class my_variant :
    public std::variant&lt;int, std::basic_string&lt;TChr&gt;&gt;
{
    // Added templating here, still fails with the same error message.
    using variant&lt;int, std::basic_string&lt;TChr&gt;&gt;::variant; 

public:
    std::basic_string&lt;TChr&gt; foo()
    {
        return &quot;&quot;;
    }
};

Doing so generates the same error message, so I must be doing something wrong.

Compiler is MSVC 2022, C++20 mode.

答案1

得分: 5

[...] 所以我一定做错了!

由于在上一 my_variant 中,父 std::variant 取决于类模板参数,您还需要来自正确父级的构造函数

template <typename TChr>
class my_variant : public std::variant<int, std::basic_string<TChr>>
{
    using std::variant<int, std::basic_string<TChr>>::variant;
    //    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
public:
    std::basic_string<TChr> foo()
    {
        return "foo";
    }
};

这已经被提及,也可以在这里阅读更多关于您的方法的信息:

https://stackoverflow.com/questions/2034916/is-it-okay-to-inherit-implementation-from-stl-containers-rather-than-delegate

英文:

> [...] So I must be doing something wrong!!

Since in the last my_variant, the parent std::variant depends upon the class template argument, you also need the constructor from the correct parent


template &lt;typename TChr&gt;
class my_variant : public std::variant&lt;int, std::basic_string&lt;TChr&gt;&gt; 
{
    using std::variant&lt;int, std::basic_string&lt;TChr&gt;&gt;::variant;
    //    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
public:
    std::basic_string&lt;TChr&gt; foo()
    {
        return &quot;foo&quot;;
    }
};

That has been mentioned, also have a read here, for more about your approach:

https://stackoverflow.com/questions/2034916/is-it-okay-to-inherit-implementation-from-stl-containers-rather-than-delegate

答案2

得分: 1

The key idea here is name lookup (名称查找). Whenever you use a name in C++, the compiler must decide what entity that name refers to. Sometimes it can just look for a declaration in the current scope (或者它的父级之一), 例如:

void foo() { return foo(); }
                    ^~~

There's no foo declared in the current scope, but there's the function named foo in the parent scope, so we take that. (在当前作用域中没有声明foo,但在父级作用域中有名为foo的函数,所以我们采用它。)

Inside a class or class template, another possibility kicks in: the injected-class-name (在类或类模板内部,还有另一种可能性:injected-class-name).

int Foo = 42;
struct Foo {
    void f(Foo&);
           ^~~

Here, the name Foo means "this class right here, Foo," even though there are multiple declarations of a name Foo in the parent scope. (C++ allows you to have a type and a variable with the same name at namespace scope, because C compatibility: think stat(3).) We call this the injected-class-name of Foo. (在这里,名称Foo表示“就是这个类,Foo”,尽管在父级作用域中有多个名称为Foo的声明。 (C++允许在命名空间范围内使用相同的名称拥有类型和变量,因为C兼容性:考虑stat(3)。) 我们称之为Foo的_injected-class-name_。)

Injected-class-names also apply to base classes (注入类名也适用于基类):

int Base = 42;
struct Base {};
struct Foo : Base {
    void f(Base&);
           ^~~~

There's no Base in the current scope, so we look in the parent scope, which is the base-class scope. There is a Base in that scope: the injected-class-name that means "this Base right here." So this code is also okay. (在当前作用域中没有Base,所以我们查找父级作用域,这是基类作用域。 在该作用域中有一个Base:表示“就在这里的Base”的_injected-class-name_。所以这段代码也是可以的。)

Inside a class template, the injected-class-name refers to "this instantiation of Foo right here." So (在类模板内部,injected-class-name 指的是“就在这里的Foo的这个_实例_”。所以:)

template<class T>
struct Foo {
    void f(Foo&);
           ^~~

this Foo means the same thing as if it had said Foo<T>. (这个Foo的意思与它说Foo<T>的意思相同。)

But remember, all of this is a subsystem of name lookup (但请记住,所有这些都是名称查找的一个子系统). The other relevant quirk of name lookup is that during phase 1 of two-phase lookup (that is, before template instantiation), name lookup never peeks into dependent base classes. (It can't, because they're dependent on template arguments, and we don't know those arguments yet.) So (Godbolt) (名称查找的另一个相关怪癖是,在two-phase lookup的第1阶段(即,在模板实例化之前),名称查找永远不会查看依赖的基类。 (不能,因为它们依赖于模板参数,而我们还不知道这些参数。) 所以 (Godbolt)):

template<class T>
struct Foo : NDBase<int>, DBase<T> {
    void f(NDBase&, DBase&);
           ^~~~~~   ^~~~~

The reference to NDBase is OK: that's the injected-class-name of NDBase<int> inherited from NDBase<int>. The reference to DBase is bad: that's not the injected-class-name of DBase<T>, because DBase<T> is a dependent base, so Foo isn't going to look up anything in it. (对NDBase的引用是可以的:这是从NDBase<int>继承的_injected-class-name_。 对DBase的引用是不好的:那不是DBase<T>的_injected-class-name_,因为DBase<T>是一个依赖的基类,所以Foo不会在其中查找任何东西。)

We can tell name lookup that DBase is supposed to be looked up as a member by prefixing it with Foo:: (for most kinds of members, prefer this->). Of course then we also need to tell the parser that this dependent name is going to be a typename, not a variable/function/etc. (我们可以通过在前面加上Foo::来告诉名称查找,DBase应该被查找为成员(对于大多数成员类型,最好使用this->)。 当然,然后我们还需要告诉解析器,这个依赖名称将是一个typename,而不是变量/函数等。):

template<class T>
struct Foo : NDBase<int>, DBase<T> {
    void f(NDBase&, typename Foo::DBase&); // OK
};

In your original example, you can get away without the typename prefix because using ... is already a typename-only context. In other words, you can do either: (在你的原始示例中,你可以不加typename前缀,因为using ...已经是一个只有typename的上下文。 换句话说,你可以这样做:)

using my_variant::variant::
英文:

To expand on JeJo's answer:

The key idea here is name lookup. Whenever you use a name in C++, the compiler must decide what entity that name refers to. Sometimes it can just look for a declaration in the current scope (or one of its parents), e.g.

void foo() { return foo(); }
                    ^~~

There's no foo declared in the current scope, but there's the function named foo in the parent scope, so we take that.

Inside a class or class template, another possibility kicks in: the injected-class-name.

int Foo = 42;
struct Foo {
    void f(Foo&amp;);
           ^~~

Here, the name Foo means "this class right here, Foo," even though there are multiple declarations of a name Foo in the parent scope. (C++ allows you to have a type and a variable with the same name at namespace scope, because C compatibility: think stat(3).) We call this the injected-class-name of Foo.

Injected-class-names also apply to base classes:

int Base = 42;
struct Base {};
struct Foo : Base {
    void f(Base&amp;);
           ^~~~

There's no Base in the current scope, so we look in the parent scope, which is the base-class scope. There is a Base in that scope: the injected-class-name that means "this Base right here." So this code is also okay.

Inside a class template, the injected-class-name refers to "this instantiation of Foo right here." So:

template&lt;class T&gt;
struct Foo {
    void f(Foo&amp;);
           ^~~

this Foo means the same thing as if it had said Foo&lt;T&gt;.

But remember, all of this is a subsystem of name lookup. The other relevant quirk of name lookup is that during phase 1 of two-phase lookup (that is, before template instantiation), name lookup never peeks into dependent base classes. (It can't, because they're dependent on template arguments, and we don't know those arguments yet.) So (Godbolt):

template&lt;class T&gt;
struct Foo : NDBase&lt;int&gt;, DBase&lt;T&gt; {
    void f(NDBase&amp;, DBase&amp;);
           ^~~~~~   ^~~~~

The reference to NDBase is OK: that's the injected-class-name of NDBase&lt;int&gt; inherited from NDBase&lt;int&gt;. The reference to DBase is bad: that's not the injected-class-name of DBase&lt;T&gt;, because DBase&lt;T&gt; is a dependent base, so Foo isn't going to look up anything in it.

We can tell name lookup that DBase is supposed to be looked up as a member by prefixing it with Foo:: (for most kinds of members, prefer this-&gt;). Of course then we also need to tell the parser that this dependent name is going to be a typename, not a variable/function/etc.:

template&lt;class T&gt;
struct Foo : NDBase&lt;int&gt;, DBase&lt;T&gt; {
    void f(NDBase&amp;, typename Foo::DBase&amp;); // OK
};

In your original example, you can get away without the typename prefix because using ... is already a typename-only context. In other words, you can do either:

using my_variant::variant::variant;
  // express that `variant` is a member, so look it up in
  // the dependent base and find it as an injected-class-name

using std::variant&lt;int, std::basic_string&lt;TChr&gt;&gt;::variant;
  // avoid injected-class-names entirely, and just name the
  // base type relative to the parent scope

But you can't just say

using variant::~~~~

because the compiler doesn't know what variant means in this scope. (Because it refuses to peek into the dependent base class in order to find that there's a matching injected-class-name visible in there.)

huangapple
  • 本文由 发表于 2023年7月4日 21:27:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76613138.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定