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

时间:2019-08-11 21:00:21

标签: c++ templates constructor c++17 sfinae

我有一个函数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>)构造函数时。

2 个答案:

答案 0 :(得分:4)

  

有什么办法可以解决这个问题?

是的。不要解决。您不应该试图猜测用户想要哪种初始化。只需这样做:

$ cd /usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/Python/
$ make regen-importlib
$ make

如果用户想使用new (ptr) T(std::forward<Args>(args)...) 构造函数,则可以传入initializer_list的实例,这样就可以了。

更有趣的情况是聚合,这就是为什么将在C ++ 20中使用括号将其初始化的原因(请参见P0960)。但这可以通过传入具有适当转换运算符的参数来解决。也就是说,如果我想构造一个:

initializer_list

并使用括号将其工作,我可以传递一个类型为

的参数
struct X { int i; };

在保证复制保留的情况下,我们无论如何都能获得正确的效果。


在任何情况下,struct X_factory { int i; operator X() const { return X{i}; } }; 实际上都不是与问题严格相关的。您可能想要的(但我不建议您这样做)是:

initializer_list

或者可能以相反的顺序写出可直接列表初始化的特征。

答案 1 :(得分:1)

@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";
}

它有很多缺陷。