如何在C++中将模板类对象作为非模板类的成员?

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

How do I have a template class object as a member of a non-template class in C++?

问题

I'm relatively new to templates in C++, and I'm having trouble understanding how to handle the following situation. I'm given a template class Reader that deduces its arguments from a template class Layout object passed to the constructor, like this:

template<typename ...Args>
class Layout {};

template<typename ...Args>
class Reader {
  public:
    Reader(Layout<Args...> layout);
};

Given a specific instantiation of Layout:

using ALayout = Layout<int, double>;

I would like to create a new, non-template class AReaderHolder that has a member variable of type Reader with ALayout as its layout. How can I do this without making AReaderHolder itself a class template?

I tried this:

class AReaderHolder {
  public:
    AReaderHolder() :
      _reader{ALayout{}} {}

  private:
    Reader _reader;
}

and got this error:

use of class template 'Reader' requires template arguments;
argument deduction not allowed in non-static class member

Which is fair, but what should I put for template arguments for the declaration for _reader? The problem as I see it is that Reader is templated on the parameter pack passed to Layout, and I don't know how to reference just that...

EDIT:
I recognize that I could just do something like

using AReader = Reader<int, double>;

And then just declare _reader to be of type AReader. But it feels clunky to manually specify the same parameter pack for ALayout and AReader (imagine the parameter pack is quite long and complex). Is there a way to do something like:

K = ???;
using ALayout = Layout<K>;
using AReader = Reader<K>;

To guarantee that ALayout and AReader are using the same parameter pack?

英文:

I'm relatively new to templates in C++, and I'm having trouble understanding how to handle the following situation. I'm given a template class Reader that deduces its arguments from a template class Layout object passed to the constructor, like this:

template&lt;typename ...Args&gt;
class Layout {};

template&lt;typename ...Args&gt;
class Reader {
  public:
	Reader(Layout&lt;Args...&gt; layout);
};

Given a specific instantiation of Layout:

using ALayout = Layout&lt;int, double&gt;;

I would like to create a new, non-template class AReaderHolder that has a member variable of type Reader with ALayout as its layout. How can I do this without making AReaderHolder itself a class template?

I tried this:

class AReaderHolder {
  public:
   	AReaderHolder() :
      _reader{ALayout{}} {}

  private:
    Reader _reader;
}

and got this error:

> use of class template 'Reader' requires template arguments;
> argument deduction not allowed in non-static class member

Which is fair, but what should I put for template arguments for the declaration for _reader? The problem as I see it is that Reader is templated on the parameter pack passed to Layout, and I don't know how to reference just that...

EDIT:
I recognize that I could just do something like

using AReader = Reader&lt;int, double&gt;;

And then just declare _reader to be of type AReader. But it feels clunky to manually specify the same parameter pack for ALayout and AReader (imagine the parameter pack is quite long and complex). Is there a way to do something like:

K = ???;
using ALayout = Layout&lt;K&gt;;
using AReader = Reader&lt;K&gt;;

To guarantee that ALayout and AReader are using the same parameter pack?

答案1

得分: 3

以下是已经翻译好的部分:

坚持制定一个AReader别名的计划:

class AReaderHolder {
    AReader _reader;
public:
    AReaderHolder() : _reader{ALayout{}} { }
};

问题是如何干净地定义AReader

您可以尝试使用类模板参数推导:

using AReader = decltype(Reader(std::declval<ALayout>()));
// 这行代码的意思是:假设您有一个ALayout。然后假设从这个虚构对象创建一个Reader。获取这个虚构Reader的类型并称其为AReader。

这取决于Reader是否具有模板推断友好的构造函数/推断指南。对于您发布的框架,这可以工作。

另一个想法是让Layout本身公开正确的Reader类型:

template<typename ...Args>
class Reader; // 必要的前向声明

template<typename... Args>
struct Layout {
    using ReaderType = Reader<Args...>;
};

using AReader = ALayout::ReaderType;

一个健壮的解决方案,不涉及更改Layout,是一个类型特性:

template<typename T>
struct ReaderForLayout;
template<typename... Args>
struct ReaderForLayout<Layout<Args...>> { using type = Reader<Args...>; };
template<typename T>
using ReaderForLayout_t = typename ReaderForLayout<T>::type;

using AReader = ReaderForLayout_t<ALayout>;

(如果我发现自己编写了像这个这样的类型特性,我会重新考虑我的设计选择。)

英文:

Sticking to the plan of making an AReader alias:

class AReaderHolder {
    AReader _reader;
public:
    AReaderHolder() : _reader{ALayout{}} { }
};

The question is how to define AReader neatly.

You could try to use class template argument deduction:

using AReader = decltype(Reader(std::declval&lt;ALayout&gt;()));
// this line says: &quot;Imagine you have an ALayout. Then imagine making a Reader from this imaginary object. Take the type of this imaginary Reader and call it AReader.&quot;

This depends on Reader having template-deduction friendly constructors/deduction guides. For the skeleton you've posted, this works.

Another idea is to have Layout itself expose the correct Reader type:

template&lt;typename ...Args&gt;
class Reader; // necessary forward declaration

template&lt;typename... Args&gt;
struct Layout {
    using ReaderType = Reader&lt;Args...&gt;;
};

using AReader = ALayout::ReaderType;

A robust solution that doesn't involve changing Layout is a type trait:

template&lt;typename T&gt;
struct ReaderForLayout;
template&lt;typename... Args&gt;
struct ReaderForLayout&lt;Layout&lt;Args...&gt;&gt; { using type = Reader&lt;Args...&gt;; };
template&lt;typename T&gt;
using ReaderForLayout_t = typename ReaderForLayout&lt;T&gt;::type;

using AReader = ReaderForLayout_t&lt;ALayout&gt;;

(If I ever found myself writing a type trait like this one, though, I would reconsider my design choices.)

答案2

得分: 0

你只需明确声明Reader的模板参数。

class AReaderHolder
{
    public:
        AReaderHolder() :
            _reader{ALayout{}}
  {}

    private:
        Reader<Layout<int, double>> _reader;
}
英文:

You just need to explicitly declare the template parameter for Reader.

class AReaderHolder
{
    public:
        AReaderHolder() :
            _reader{ALayout{}}
  {}

    private:
        Reader&lt;Layout&lt;int,double&gt;&gt; _reader;
}

答案3

得分: 0

cpp/class template上看到,问题源于Reader是一个模板类,当你在AReaderHolder类中声明其实例时,它需要模板参数。

在你的情况下,由于你已经将ALayout定义为Layout&lt;int, double&gt;,你可以将其用作Reader的模板参数。

例如:

class AReaderHolder
{
    public:
        AReaderHolder() :
            _reader{ALayout{}}
        {}

    private:
        Reader&lt;int, double&gt; _reader; // 在这里使用ALayout的模板参数
};

在上面的代码中,_readerReader&lt;int, double&gt;的实例,因为ALayoutLayout&lt;int, double&gt;的别名。这样,AReaderHolder不需要是一个模板类,而且根据你的ALayout定义,为Reader提供了明确的模板参数。


我们之所以使用Reader&lt;int, double&gt; _reader;而不是Reader&lt;Layout&lt;int, double&gt;&gt; _reader;,与Reader类模板的定义方式有关。

看一下你最初的Reader定义:

template&lt;typename ...Args&gt;
class Reader 
{
  public:
	Reader(Layout&lt;Args...&gt; layout);
};

Reader的模板参数不是Layout对象,而是用于实例化Layout的类型。在构造函数中,传递了一个Layout&lt;Args...&gt;对象,但ReaderLayout中的Args...是相同的。

因此,当我们实例化Reader时,我们需要提供用于实例化Layout的类型,而不是Layout本身。因此,我们使用Reader&lt;int, double&gt; _reader;,因为intdouble是用于实例化Layout(即ALayout)的类型。


总结一下:AReaderHolder是一个非模板类,具有类型为Reader&lt;int, double&gt;的成员变量_reader。这个Reader&lt;int, double&gt;Reader模板类的实例,对应于你定义的Layout&lt;int, double&gt;ALayout

更准确地说,如果你想确保_readerALayout明确关联,你可以为Reader&lt;int, double&gt;定义一个类型别名,并在AReaderHolder中使用它:

using AReader = Reader&lt;int, double&gt;;

class AReaderHolder
{
    public:
        AReaderHolder() :
            _reader{ALayout{}}
        {}

    private:
        AReader _reader;
};

这样,清楚地表明_reader是与ALayout对应的Reader版本。然而,在底层,AReader仍然是具有与ALayout相同参数的Reader模板类的实例。


> 有没有办法做这样的事情:
> cpp &gt; K = ???; &gt; using ALayout = Layout&lt;K&gt;; &gt; using AReader = Reader&lt;K&gt;; &gt;
> 以确保ALayoutAReader使用相同的参数包?

我不认为你可以像在你的例子中那样为K这样的变量分配类型(或者在参数包的情况下分配一组类型)。

也许类型别名在你的情况下会有帮助:

template&lt;typename ...Args&gt;
using ALayout = Layout&lt;Args...&gt;;

template&lt;typename ...Args&gt;
using AReader = Reader&lt;Args...&gt;;

// 现在你可以这样定义你的类型
using MyLayout = ALayout&lt;int, double&gt;;
using MyReader = AReader&lt;int, double&gt;;

在这个例子中,MyLayoutMyReader都使用相同的参数包,你只需要指定一次。如果你需要更改参数包,只需在一个地方更改即可。但是,请注意MyLayoutMyReader仍然是不同的类型,更改一个的定义不会自动更改另一个。


阅读关于type trait,我想你可以使用类型特性从Layout中提取类型并用它们定义Reader

类型特性是在类型上操作的模板,它可以用于定义类型转换或提取有关类型的信息。

对于你的情况,你可以定义一个类型特性,从Layout中提取参数包并应用于Reader,如下所示:

template&lt;typename T&gt;
struct LayoutToReader;  // 前向声明

template&lt;typename ...Args&gt;
struct LayoutToReader&lt;Layout&lt;Args...&gt;&gt; {
    using type = Reader&lt;Args...&gt;;
};

// 现在你可以这样定义AReader:
using AReader = LayoutToReader&lt;ALayout&gt;::type;

在这个例子中,LayoutToReader是一个类型特性,它将Layout转换为相应的Reader。它通过专门化Layout&lt;Args...&gt;并定义一个type成员,该成员是Reader&lt;Args...&gt;来完成这一转换。然后,你可以使用这个类型特性基于ALayout定义AReader

这样,你就不需要手动为ALayoutAReader指定相同的参数包。如果ALayout更改,AReader将自动更改以匹配它。这是一个更健壮的解决方案,即使参数包很长且复杂也能正常工作。

英文:

Looking at cpp / class template, I see the issue comes from the fact that Reader is a template class, and it requires template arguments when you are declaring an instance of it in your AReaderHolder class.

In your case, since you have already defined ALayout as Layout&lt;int, double&gt;, you can use it as the template argument for Reader.

For instance:

class AReaderHolder
{
    public:
        AReaderHolder() :
            _reader{ALayout{}}
        {}

    private:
        Reader&lt;int, double&gt; _reader; // Using ALayout template arguments here
};

In the above code, _reader is an instance of Reader&lt;int, double&gt;, because ALayout is an alias for Layout&lt;int, double&gt;.
This way, AReaderHolder does not need to be a template class, and the template arguments for Reader are provided explicitly based on your ALayout definition.


The reason we have Reader&lt;int, double&gt; _reader; instead of Reader&lt;Layout&lt;int, double&gt;&gt; _reader; has to do with how the Reader class template is defined.

Looking at your initial Reader definition:

template&lt;typename ...Args&gt;
class Reader 
{
  public:
	Reader(Layout&lt;Args...&gt; layout);
};

The template parameters for Reader are not a Layout object, but rather the types that are used to instantiate Layout. In the constructor, an object of Layout&lt;Args...&gt; is passed, but the Args... in Reader and Layout are the same.

So, when we instantiate Reader, we need to provide the types that we would use to instantiate Layout, not the Layout itself. Therefore, we use Reader&lt;int, double&gt; _reader; because int and double are the types that are used to instantiate Layout (i.e., ALayout).


To resume: AReaderHolder is a non-template class that has a member variable _reader of type Reader&lt;int, double&gt;. This Reader&lt;int, double&gt; is the instantiation of the Reader template class that corresponds to the Layout&lt;int, double&gt; or ALayout that you have defined.

To be more precise, if you want to make sure that _reader is specifically associated with ALayout, you could potentially define a type alias for Reader&lt;int, double&gt; and use that in AReaderHolder:

using AReader = Reader&lt;int, double&gt;;

class AReaderHolder
{
    public:
        AReaderHolder() :
            _reader{ALayout{}}
        {}

    private:
        AReader _reader;
};

This way, it's clear that _reader is the version of Reader that corresponds to ALayout. However, under the hood, AReader is still an instantiation of the Reader template class with the same arguments as ALayout.


> Is there a way to do something like:
> cpp
&gt; K = ???;
&gt; using ALayout = Layout&lt;K&gt;;
&gt; using AReader = Reader&lt;K&gt;;
&gt;

> To guarantee that ALayout and AReader are using the same parameter pack?

I do not think you can assign a type (or a set of types, in the case of a parameter pack) to a variable like K in your example.

Maybe a type alias would help in your case:

template&lt;typename ...Args&gt;
using ALayout = Layout&lt;Args...&gt;;

template&lt;typename ...Args&gt;
using AReader = Reader&lt;Args...&gt;;

// Now you can define your types as
using MyLayout = ALayout&lt;int, double&gt;;
using MyReader = AReader&lt;int, double&gt;;

In this example, MyLayout and MyReader are both using the same parameter pack, and you only have to specify it once.
If you need to change the parameter pack, you only have to change it in one place.
However, note that MyLayout and MyReader are still separate types, and changing the definition of one will not automatically change the other.


Reading about type trait, I suppose you can use a type trait to extract the types from the Layout and use them to define Reader.
A type trait is a template that operates on types, and it can be used to define type transformations or to extract information about a type.

For your case, you can define a type trait that extracts the parameter pack from Layout and applies it to Reader, like:

template&lt;typename T&gt;
struct LayoutToReader;  // Forward declaration

template&lt;typename ...Args&gt;
struct LayoutToReader&lt;Layout&lt;Args...&gt;&gt; {
    using type = Reader&lt;Args...&gt;;
};

// Now you can define AReader like this:
using AReader = LayoutToReader&lt;ALayout&gt;::type;

In this example, LayoutToReader is a type trait that transforms a Layout into a corresponding Reader. It does this by specializing for Layout&lt;Args...&gt; and defining a type member that is Reader&lt;Args...&gt;. Then, you can use this type trait to define AReader based on ALayout.

This way, you don't need to manually specify the same parameter pack for ALayout and AReader. If ALayout changes, AReader will automatically change to match it. This is a more robust solution that works even if the parameter pack is long and complex.

答案4

得分: 0

Perhaps a simpler approach than the so far suggested would be to parameterize Reader not on ...Args but on Layout:

template &lt;typename Layout&gt;
class Reader { /* ... */ };

Then you can just instantiate Reader on ALayout:

class AReaderHolder {
  public:
    AReaderHolder() :
      _reader{ALayout{}} {}

  private:
    Reader&lt;ALayout&gt; _reader;
};

If you need access to the template arguments ...Args of ALayout in the definition of Reader, you can specialize it and leave the primary template undefined:

template &lt;typename Layout&gt;
class Reader;

template &lt;typename... T&gt;
class Reader&lt;Layout&lt;T...&gt;&gt; { /* Here you have access to `...Args` */ };
英文:

Perhaps a simpler approach than the so far suggested would be to parameterize Reader not on ...Args but on Layout:

template &lt;typename Layout&gt;
class Reader { /* ... */ };

Then you can just instantiate Reader on ALayout:

class AReaderHolder {
  public:
    AReaderHolder() :
      _reader{ALayout{}} {}

  private:
    Reader&lt;ALayout&gt; _reader;
};

If you need access to the template arguments ...Args of ALayout in the definition of Reader, you can specialize it and leave the primary template undefined:

template &lt;typename Layout&gt;
class Reader;

template &lt;typename... T&gt;
class Reader&lt;Layout&lt;T...&gt;&gt; { /* Here you have access to `...Args` */ };

huangapple
  • 本文由 发表于 2023年5月22日 04:01:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76301705.html
匿名

发表评论

匿名网友

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

确定