我有一个函数func <T> (...)
必须分成两个分支;
第一个分支:输入
T
时的情况有T(std::initializer_list<U>)
构造函数。第二个分支:输入
T
时的情况没有T(std::initializer_list<U>)
构造函数。
我当前的实现如下:
template<typename T, typename U>
using has_init_list_ctor = std::enable_if_t<std::is_constructible_v<
T,
std::initializer_list<U>
>>;
// version for T with initialization list ctor
template<
typename T,
typename = std::enable_if_t<
std::is_detected_v<has_init_list_ctor, T, /* idk how to auto-deduce type U */>
>
>
void func() {
//...
}
// version for T without initialization list ctor
template<
typename T,
typename = std::enable_if_t<
!std::is_detected_v<has_init_list_ctor, T, /* idk how to auto-deduce type U */>
>
>
void func() {
//...
}
但它有一个缺陷。我不知道如何自动推断类型 U
来自T
.
理想的解决方案是:
template<typename T>
struct deduce_U_from_T
{
// implementation.
usign type = /* ??? */;
};
template<typename T>
using has_init_list_ctor = std::enable_if_t<std::is_constructible_v<
T,
std::initializer_list<
typename deduce_U_from_T<T>::type
>
>>;
但我不知道如何实现deduce_U_from_T
。
有什么办法可以解决这个问题吗? 或者有什么解决方法吗?
更新:
函数func <T> (...)
是 std::alocator_traits::construct()
的仿制品。
我正在尝试实现我自己的“分配器”以使用 std::vector
和智能指针。一般情况下我会使用默认的std::alocator_traits
,但这一次,我需要从“特殊”池中请求内存(这是我实现的东西,可以称为“虚拟堆”,通过像 T * get_memory <T> (...)
这样的方法访问,池在 mem 期间执行额外的操作分配,并提供不同的分配“模式” - 很抱歉我说得很笼统,但目前它是 WIP,并且不断变化)
func <T> (...)
的简单实现(allocator_traits::construct()
)
template<typename T>
class allocator_traits
{
//...
public:
template<typename... Args>
static
std::enable_if_t<
std::is_detected_v<has_init_list_ctor, T>,
void
> construct(T * ptr, Args && ... args)
{
new(ptr) T(std::forward<Args>(args)...); // normal brackets // construct with placment-new
}
template<typename... Args>
static
std::enable_if_t<
!std::is_detected_v<has_init_list_ctor, T>,
void
> construct(T * ptr, Args && ... args)
{
new(ptr) T{ std::forward<Args>(args)... }; // curly brackets // construct with placment-new
}
//...
};
区别在于构造类型 T
的能力带大括号(当类型 T
没有 T(std::initializer_list<U>)
构造函数时。
最佳答案
Is there any way to solve this problem?
是的。别解决它。您不应该试图猜测用户想要什么样的初始化。只需这样做:
new (ptr) T(std::forward<Args>(args)...)
如果用户想要使用 initializer_list
构造函数,他们可以传入 initializer_list
的实例,这样就可以正常工作。
更有趣的情况是聚合,这就是为什么它们在 C++20 中可以用括号初始化(请参阅 P0960 )。但这可以通过传入具有适当转换运算符的参数来解决。也就是说,如果我想构造一个:
struct X { int i; };
并使其与括号一起使用,我可以传入类型的参数:
struct X_factory { int i; operator X() const { return X{i}; } };
并且通过保证复制省略,我们无论如何都会得到正确的效果。
无论如何,initializer_list
实际上与问题并不严格相关。您可能想要的(我不建议这样做)是:
if constexpr (std::is_constructible_v<T, Args...>) {
new (ptr) T(std::forward<Args>(args)...);
} else {
new (ptr) T{std::forward<Args>(args)...};
}
或者可能以相反的顺序编写直接列表初始化的特征。
关于c++ - 如何检查类型 'T' 是否有 'T(std::initializer_list<U>)' 构造函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57453579/