确保模板参数类型与其可变参数构造函数的类型相匹配

时间:2019-05-26 06:35:22

标签: c++ templates c++17 variadic-functions enable-if

我想要一个像这样的课程:

template<typename T>
struct Foo {
    T* data_;

    template<typename... Ts, std::enable_if<std::is_same<T,Ts>...>...>
    explicit Foo(Ts...ts) : data_{ ts... } {}
};

但是;语法有些错误,并且我不确定是否可以在初始化时像这样直接将参数设置为指针。

我想做的就是这样:

Foo<int> f1{ 1, 3, 5, 7 }; // Or
// Foo<int> f1( 1, 3, 5 7 );
// f1.data_[0] = 1
// f1.data_[1] = 3
// f1.data_[2] = 5
// f1.data_[3] = 7
// f1.data_[4] = ... not our memory either garbage or undefined...

Foo<float> f2{ 3.5f, 7.2f, 9.8f }; // Or
// Foo<float> f2( 3.5f, 7.2f, 9.8f );
// f2.data_[0] = 3.5
// f2.data_[1] = 7.2
// f2.data_[2] = 9.8
// f2.data_[3] = ... not our memory

我还希望检查构造函数,以确保传递给构造函数的每个参数的类型均为<T>;只需为每个Ts放一个,它就必须是T

我可能对此想法太过思索,但对于我一生来说,我无法获得此或类似于编译的内容。我不知道它是在enable_ifis_same中还是通过类的初始化程序列表并尝试将内容存储到指针中。我不知道是否应该使用T数组,但要等到参数传入构造函数后才能知道数组的大小。我也尝试不使用诸如std::vector之类的基本容器来执行此操作;它更多是用于自我教育,而不是实用的源代码。我只想看看如何使用原始指针来完成此操作。


修改

我已经将班级更改为以下内容:

template<typename T>
struct Foo {
    T* data_;

    template<typename... Ts, std::enable_if_t<std::is_same<T, Ts...>::value>* = nullptr>
    explicit Foo( const Ts&&... ts ) : data_{ std::move(ts)... } {}
 };

当尝试使用它时:

 int a = 1, b = 3, c = 5, d = 7;
 Foo<int> f1( a, b, c, d );
 Foo<int> f2{ a, b, c, d };

这次迭代让我更加接近;但是它们都给出不同的编译器错误。

  • 第一个是:C2661“没有重载函数需要4个参数”
  • 第二个:C2440“正在初始化,无法从初始值设定项列表转换为Container,没有构造函数可以采用源类型,或者构造函数重载解析度不明确。”

4 个答案:

答案 0 :(得分:4)

为什么不简单地使用max_execution_time = 600 max_input_time = 600 memory_limit = 1024M post_max_size = 1024M

max_allowed_packet = 1024M

如果某些std::initialize_list:#include <iostream> #include <type_traits> #include <vector> template <class T> struct Foo { std::vector<T> data_; explicit Foo(std::initializer_list<T> data) : data_(data) { std::cout << "1"; }; template <typename... Ts, typename ENABLE=std::enable_if_t<(std::is_same_v<T,Ts> && ...)> > explicit Foo(Ts... ts) : Foo(std::initializer_list<T>{ts...}) { std::cout << "2"; } }; int main() { Foo<int> f1{1, 3, 5, 7}; // prints 1 Foo<int> f2(1, 3, 5, 7); // prints 1 then 2 return 0; } 不同,则会出现编译时错误。

使用

Ts

您得到:

T
  

错误:内部将“ 5.0e + 0”的转换范围从“ double”缩小为“ int”   {} [-Wnarrowing] Foo f1 {1、3、5、7};                             ^

gcc -std=c++17  prog.cpp  

您得到

  

错误:调用‘Foo :: Foo(int,int,   double,int)’Foo f2(1、3、5、7);                             ^ note:候选人:“ template Foo :: Foo(Ts ...)”显式Foo(Ts ... ts):   Foo(std :: initializer_list {ts ...})

     

...

更新:如果您真的想使用原始指针之类的示例,请参见以下完整示例:

  Foo<int> f1{1, 3, 5., 7};

我让您用原始指针代替所有其他工作来替换unique_ptr:delete []等...

答案 1 :(得分:2)

std::is_same仅比较两种类型,并且不能使用包扩展来声明多个模板参数。这意味着您需要将所有std::is_same支票拉出到另一张支票中:

template <typename T, typename... Ts>
struct all_same : std::bool_constant<(std::is_same<T, Ts>::value && ...)> {};

template <typename T>
struct Foo
{
    std::vector<T> data_;

    template <typename... Ts, std::enable_if_t<all_same<T, std::decay_t<Ts>...>::value>* = nullptr>
    Foo(Ts&&... ts)
        : data_{std::forward<Ts>(ts)...}
    {
    }
};

Live Demo

您还需要为data_数组分配内存。在这里,我使用std::vector来照顾我的分配,但是如果您确实愿意,可以使用new[]delete[]自己进行管理。

答案 2 :(得分:1)

enable_ifis_same不会在任何地方存储任何内容,它们只是编译时构造,不会产生二进制可执行文件中的任何代码。

不管语法如何,您的代码本质上是在尝试获取构造函数参数的地址(这是临时的)。构造函数退出后,这将是一个悬空指针。

要么Foo拥有内存区域,并且必须在构造函数中分配并在析构函数中删除(如有疑问:请使用std::vector!),否则它会别名一些外部内存,并且必须接收一个指向该内存的指针。记忆。

现在有关语法:

  • std::is_same是一个提供value布尔常量的模板,其用法如下:std::is_same<T1, T2>::value。或者,您可以使用std::is_same_v<T1, T2>
  • std::enable_if仅在常量表达式(第一个模板参数)为true时提供type类型的成员。像std::enable_if<expr, T>::type一样使用它。如果expr为true,则typeT的typedef。否则,它不会被定义并产生替换失败。或者,您可以使用std::enable_if_t<expr, T>

您可以here来看看您的类似方法。

但是您也可以通过使用成员向量来简化所有这些操作。在这种情况下,向量构造函数可确保所有参数都具有兼容的类型。这是一个完整的示例:

#include <string>
#include <vector>
#include <iostream>
#include <type_traits>

using namespace std;

template<typename T>
struct Foo {
    vector<T> data_;

    template<typename ...Ts>
    explicit Foo(Ts... ts) : data_{ ts... } {}

    void print() {
        for (const auto &v : data_) {
            cout << v << " ";
        }
        cout << endl;
    }
};

int main() {

    Foo<int> ints { 1, 2, 3, 4, 5 };
    Foo<string> strings { "a", "b", "c", "d", "e"};
    // Foo<string> incorrect { "a", 2, "c", 4, "e"};

    ints.print();
    strings.print();
    // incorrect.print();

    return 0;
}

答案 3 :(得分:0)

在C ++ 17中,最惯用的方法是使用std::cunjunction_v。它懒惰地评估后续值,并允许您避免折叠表达式。对于您在已编辑的代码段中提到的两种情况,编译器生成的消息也是相同的。

此外,通过const-rvalue-ref传递数据没有任何意义,因为不可能从const对象中移动数据。另外,您正在将参数打包移动到指针。我不知道该怎么办,因此将其删除。

另外,请看一下std::decay_t-没有它就无法正常工作,因为有时Ts不是推导为int而是推导为const int &或{{1 }}。这导致int &为假。

下面的代码可以在Godbolt上很好地编译:

std::is_same_v