如何检查类型 'T' 是否具有 'T(std::initializer_list<U>)' 构造函数

how to check if type 'T' has 'T(std::initializer_list<U>)' constructor

我有一个函数func <T> (...),它必须分成两个分支;

我目前的实现如下:

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() {
    //... 
}

但它有一个缺陷。我不知道如何从 T.

自动推断类型 U

理想的解决方案是:

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() 的模仿。 我正在尝试实现我自己的 "allocator" 以使用 std::vector 和智能指针。一般情况下,我会使用默认的std::alocator_traits,但是这次,我需要从"special"池中申请内存(这是我实现的东西,可以调用"virtual heap",它通过 T * get_memory <T> (...) 等方法访问,池在 mem 分配期间执行额外的操作,并提供不同的 "modes" 分配 - 我很抱歉非常通用,但目前它是 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)...};
}

或者可能以相反的顺序为 direct-list-initializable 写一个 trait。

@Barry 是对的,这是个坏主意。

操作方法如下:

#include <initializer_list>
#include <utility>
#include <iostream>

struct any_il{
    template<class U>
    operator std::initializer_list<U>()const{ return {}; }
};

struct foo {
    foo(std::initializer_list<int>){}
    foo(foo&&)=default;
};

template<class T, class=void>
struct can_from_il:std::false_type{};

template<class T>
struct can_from_il<T, decltype(void( T(any_il{}) ) )>:std::true_type{};

int main(){
    std::cout << can_from_il<foo>{}() << can_from_il<int>{}() <<"\n";
}

它有很多缺陷。