英文:
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<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?
答案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<ALayout>()));
// this line says: "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."
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<typename ...Args>
class Reader; // necessary forward declaration
template<typename... Args>
struct Layout {
using ReaderType = Reader<Args...>;
};
using AReader = ALayout::ReaderType;
A robust solution that doesn't involve changing Layout
is a type trait:
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>;
(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<Layout<int,double>> _reader;
}
答案3
得分: 0
在cpp/class template上看到,问题源于Reader
是一个模板类,当你在AReaderHolder
类中声明其实例时,它需要模板参数。
在你的情况下,由于你已经将ALayout
定义为Layout<int, double>
,你可以将其用作Reader
的模板参数。
例如:
class AReaderHolder
{
public:
AReaderHolder() :
_reader{ALayout{}}
{}
private:
Reader<int, double> _reader; // 在这里使用ALayout的模板参数
};
在上面的代码中,_reader
是Reader<int, double>
的实例,因为ALayout
是Layout<int, double>
的别名。这样,AReaderHolder
不需要是一个模板类,而且根据你的ALayout
定义,为Reader
提供了明确的模板参数。
我们之所以使用Reader<int, double> _reader;
而不是Reader<Layout<int, double>> _reader;
,与Reader
类模板的定义方式有关。
看一下你最初的Reader
定义:
template<typename ...Args>
class Reader
{
public:
Reader(Layout<Args...> layout);
};
Reader
的模板参数不是Layout
对象,而是用于实例化Layout
的类型。在构造函数中,传递了一个Layout<Args...>
对象,但Reader
和Layout
中的Args...
是相同的。
因此,当我们实例化Reader
时,我们需要提供用于实例化Layout
的类型,而不是Layout
本身。因此,我们使用Reader<int, double> _reader;
,因为int
和double
是用于实例化Layout
(即ALayout
)的类型。
总结一下:AReaderHolder
是一个非模板类,具有类型为Reader<int, double>
的成员变量_reader
。这个Reader<int, double>
是Reader
模板类的实例,对应于你定义的Layout<int, double>
或ALayout
。
更准确地说,如果你想确保_reader
与ALayout
明确关联,你可以为Reader<int, double>
定义一个类型别名,并在AReaderHolder
中使用它:
using AReader = Reader<int, double>;
class AReaderHolder
{
public:
AReaderHolder() :
_reader{ALayout{}}
{}
private:
AReader _reader;
};
这样,清楚地表明_reader
是与ALayout
对应的Reader
版本。然而,在底层,AReader
仍然是具有与ALayout
相同参数的Reader
模板类的实例。
> 有没有办法做这样的事情:
> cpp > K = ???; > using ALayout = Layout<K>; > using AReader = Reader<K>; >
> 以确保ALayout
和AReader
使用相同的参数包?
我不认为你可以像在你的例子中那样为K
这样的变量分配类型(或者在参数包的情况下分配一组类型)。
也许类型别名在你的情况下会有帮助:
template<typename ...Args>
using ALayout = Layout<Args...>;
template<typename ...Args>
using AReader = Reader<Args...>;
// 现在你可以这样定义你的类型
using MyLayout = ALayout<int, double>;
using MyReader = AReader<int, double>;
在这个例子中,MyLayout
和MyReader
都使用相同的参数包,你只需要指定一次。如果你需要更改参数包,只需在一个地方更改即可。但是,请注意MyLayout
和MyReader
仍然是不同的类型,更改一个的定义不会自动更改另一个。
阅读关于type trait,我想你可以使用类型特性从Layout
中提取类型并用它们定义Reader
。
类型特性是在类型上操作的模板,它可以用于定义类型转换或提取有关类型的信息。
对于你的情况,你可以定义一个类型特性,从Layout
中提取参数包并应用于Reader
,如下所示:
template<typename T>
struct LayoutToReader; // 前向声明
template<typename ...Args>
struct LayoutToReader<Layout<Args...>> {
using type = Reader<Args...>;
};
// 现在你可以这样定义AReader:
using AReader = LayoutToReader<ALayout>::type;
在这个例子中,LayoutToReader
是一个类型特性,它将Layout
转换为相应的Reader
。它通过专门化Layout<Args...>
并定义一个type
成员,该成员是Reader<Args...>
来完成这一转换。然后,你可以使用这个类型特性基于ALayout
定义AReader
。
这样,你就不需要手动为ALayout
和AReader
指定相同的参数包。如果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<int, double>
, you can use it as the template argument for Reader
.
For instance:
class AReaderHolder
{
public:
AReaderHolder() :
_reader{ALayout{}}
{}
private:
Reader<int, double> _reader; // Using ALayout template arguments here
};
In the above code, _reader
is an instance of Reader<int, double>
, because ALayout
is an alias for Layout<int, double>
.
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<int, double> _reader;
instead of Reader<Layout<int, double>> _reader;
has to do with how the Reader
class template is defined.
Looking at your initial Reader
definition:
template<typename ...Args>
class Reader
{
public:
Reader(Layout<Args...> 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<Args...>
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<int, double> _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<int, double>
. This Reader<int, double>
is the instantiation of the Reader
template class that corresponds to the Layout<int, double>
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<int, double>
and use that in AReaderHolder
:
using AReader = Reader<int, double>;
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
> K = ???;
> using ALayout = Layout<K>;
> using AReader = Reader<K>;
>
> 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<typename ...Args>
using ALayout = Layout<Args...>;
template<typename ...Args>
using AReader = Reader<Args...>;
// Now you can define your types as
using MyLayout = ALayout<int, double>;
using MyReader = AReader<int, double>;
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<typename T>
struct LayoutToReader; // Forward declaration
template<typename ...Args>
struct LayoutToReader<Layout<Args...>> {
using type = Reader<Args...>;
};
// Now you can define AReader like this:
using AReader = LayoutToReader<ALayout>::type;
In this example, LayoutToReader
is a type trait that transforms a Layout
into a corresponding Reader
. It does this by specializing for Layout<Args...>
and defining a type
member that is Reader<Args...>
. 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 <typename Layout>
class Reader { /* ... */ };
Then you can just instantiate Reader
on ALayout
:
class AReaderHolder {
public:
AReaderHolder() :
_reader{ALayout{}} {}
private:
Reader<ALayout> _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 <typename Layout>
class Reader;
template <typename... T>
class Reader<Layout<T...>> { /* 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 <typename Layout>
class Reader { /* ... */ };
Then you can just instantiate Reader
on ALayout
:
class AReaderHolder {
public:
AReaderHolder() :
_reader{ALayout{}} {}
private:
Reader<ALayout> _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 <typename Layout>
class Reader;
template <typename... T>
class Reader<Layout<T...>> { /* Here you have access to `...Args` */ };
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论