英文:
How to deduce the type of template argument based on some conditions and return information about that type
问题
假设我有一个类 ```C```,它包含一个 ```std::variant<A, B>```。```C``` 的构造函数必须是一个模板构造函数,可以接受任何类型,并根据该类型以不同的方式初始化 variant。
这是一个简化的概述:
```cpp
struct A {
// ...
};
struct B {
// ...
};
class C {
public:
template <typename T>
C(T arg) {
if constexpr (std::same_v<B, T>) {
var = B{arg};
}
else if constexpr (std::is_constructible<A, T>) {
var = A{arg};
}
}
private:
std::variant<A, B> var;
};
我正在寻找一种使用一些模板元编程技巧来合并这些 if constexpr 语句的方法,以便我可以像这样重写构造函数(希望避免额外初始化 variant):
template<T>
struct DeduceAOrB {
// ..... somehow check the conditions here
};
template <typename T>
C(T arg)
: var(DeduceAOrB<T>::type{arg})
{}
重要注意事项是 variant 可能在将来需要扩展,因此解决方案必须适用于任意多个类型。
英文:
Suppose I have a class C
that has holds a std::variant<A, B>
. The constructor of C
must be a templated constructor that can accept any type, and based on that type it must initialize the variant in different ways.
Here is a simplified overview:
struct A {
// ...
};
struct B {
// ...
};
class C {
public:
template <typename T>
C(T arg) {
if constexpr (std::same_v<B, T>) {
var = B{arg};
}
else if constexpr (std::is_constructible<A, T>) {
var = A{arg};
}
}
private:
std::variant<A, B> var;
};
I am looking for a way to squash these if constexpr statements by using some template metaprogramming magic, so I may rewrite the constructor like this (hoping to avoid the extra initialization of the variant):
template<T>
struct DeduceAOrB {
// ..... somehow check the conditions here
};
template <typename T>
C(T arg)
: var(DeduceAOrB<T>::type{arg})
{}
Important note is that the variant may need to be expanded in the future, so the solution must be viable for arbitrarily many types
答案1
得分: 3
以下是翻译好的代码部分:
class C {
private:
// 包含条件并根据结果返回的辅助函数
template<typename T>
static constexpr auto helperFunc(T arg)
{
if constexpr (std::is_same_v<B, T>) {
return B{arg};
}
else if constexpr (std::is_constructible_v<A, T>) {
return A{arg};
} else {
return A{};
}
}
public:
template <typename T>
//----------------VVV------------------> 在初始化列表中使用 helperFunc
C(T arg): var(helperFunc(arg)){
}
private:
std::variant<A, B> var;
};
英文:
One way is to create a helper function containing the conditions and use it in initializer list as shown below:
class C {
private:
//helper function checking for different conditions and returning based on result
template<typename T>
static constexpr auto helperFunc(T arg)
{
if constexpr (std::is_same_v<B, T>) {
return B{arg};
}
else if constexpr (std::is_constructible_v<A, T>) {
return A{arg};
} else {
return A{};
}
}
public:
template <typename T>
//----------------VVV------------------>use helperFunc initializer list
C(T arg): var(helperFunc(arg)){
}
private:
std::variant<A, B> var;
};
答案2
得分: 1
std::conditional
是为了处理这种情况而创建的。它的 type
会根据提供的条件选择两种类型中的一种:
std::conditional_t<boolean,
true-type,
false-type>
当然,你可以嵌套使用它们,根据需要可以有多个条件,就像这种情况:
std::conditional_t<boolean1,
true-type1,
std::conditional_t<boolean2,
true-type2,
false-type>>
因此,DeduceAOrB
可以这样定义:
template<class T>
using DeduceAOrB =
std::conditional_t<std::is_same_v<B, std::remove_cv_t<std::remove_reference_t<T>>>,
B,
std::conditional_t<std::is_constructible_v<A, T>,
A,
defaulter<A>>>;
你可以将当前使用 if constexpr
的构造函数重写如下:
template <class T>
C(T&& arg) : var{DeduceAOrB<T>{std::forward<T>(arg)}} {}
它使用了与你构造函数中使用的逻辑完全相同的逻辑:
- 如果
std::is_same_v<B, T>
为真 - 从arg
构造一个B
,否则 - 如果
std::is_constructible_v<A, T>
为真 - 从arg
构造一个A
,否则 - 默认构造一个
A
defaulter
类模板用于创建一个可以从任何参数构造并可以隐式转换为 T
的类型,这用于在两个条件都失败的情况下默认构造一个 A
:
template <class T>
struct defaulter {
template <class... Args> defaulter(Args&&...) {}
operator T() const { return T{}; }
};
英文:
> I am looking for a way to squash these if constexpr statements by using some template metaprogramming magic
std::conditional
was created for cases like this. It's type
will be one of two supplied types depending on the supplied condition:
std::conditional_t<boolean,
true-type,
false-type>
and you can of course nest them with as many conditions as you'd like, which will be needed in this case:
std::conditional_t<boolean1,
true-type1,
std::conditional_t<boolean2,
true-type2,
false-type>>
DeduceAOrB
could therefore be defined as such:
template<class T>
using DeduceAOrB =
std::conditional_t<std::is_same_v<B, std::remove_cv_t<std::remove_reference_t<T>>>,
B,
std::conditional_t<std::is_constructible_v<A, T>,
A,
defaulter<A>>>;
Your current constructor using if constexpr
could then be rewritten like so:
template <class T>
C(T&& arg) : var{DeduceAOrB<T>{std::forward<T>(arg)}} {}
It uses the exact same logic as you use in your constructor:
- if
std::is_same_v<B, T>
- construct aB
fromarg
, else - if
std::is_constructible_v<A, T>
- construct anA
fromarg
, else - default construct an
A
The defaulter
class template is used to create a type that can be constructed from any arguments and is implicitly convertible to T
, which is used to default construct an A
in case both conditions fails.
template <class T>
struct defaulter {
template <class... Args> defaulter(Args&&...) {}
operator T() const { return T{}; }
};
答案3
得分: 0
如果这只是一次性的操作,你可以像下面展示的那样使用 lambda。否则,你可以创建一个辅助函数。
class C {
public:
template <typename T>
//--------------v-------------------->使用构造函数初始化列表
C(T arg): var([&](){
if constexpr (std::is_same_v<B, T>) {
return B{arg};
}
else if constexpr (std::is_constructible_v<A, T>) {
return A{arg};
}}()) {
}
private:
std::variant<A, B> var;
};
英文:
If this is a one time thing, you can use lambda as shown below. Otherwise you can create a helper function.
class C {
public:
template <typename T>
//--------------v-------------------->use constructor initializer list
C(T arg): var([&](){
if constexpr (std::is_same_v<B, T>) {
return B{arg};
}
else if constexpr (std::is_constructible_v<A, T>) {
return A{arg};
}}()) {
}
private:
std::variant<A, B> var;
};
答案4
得分: 0
这是作为类型包装器的std::type_identity
的一个候选项:
template<class T>
struct DeduceAOrB : decltype([]
{
if constexpr (std::same_v<B, T>) {
return std::type_identity<B>();
} else if constexpr (std::is_constructible<A, T>) {
return std::type_identity<A>();
}
}()) {
// 'type' alias inherited from 'std::type_identity' base class
};
如果你愿意,甚至可以内联这个决策:
class C {
public:
template <typename T>
C(T arg) : var(typename decltype([&]
{
if constexpr (std::same_v<B, T>) {
return std::type_identity<B>();
} else if constexpr (std::is_constructible<A, T>) {
return std::type_identity<A>();
}
}())::type{arg}) {
}
private:
std::variant<A, B> var;
};
英文:
This is a candidate for std::type_identity
as a wrapper for types:
template<class T>
struct DeduceAOrB : decltype([]
{
if constexpr (std::same_v<B, T>) {
return std::type_identity<B>();
} else if constexpr (std::is_constructible<A, T>) {
return std::type_identity<A>();
}
}()) {
// 'type' alias inherited from 'std::type_identity' base class
};
You can even inline this decision, should you want to:
class C {
public:
template <typename T>
C(T arg) : var(typename decltype([&]
{
if constexpr (std::same_v<B, T>) {
return std::type_identity<B>();
} else if constexpr (std::is_constructible<A, T>) {
return std::type_identity<A>();
}
}())::type{arg}) {
}
private:
std::variant<A, B> var;
};
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论