在其他类中实现std :: array-like构造函数

时间:2014-01-24 21:11:22

标签: c++ arrays c++11 variadic-templates

在我使用的所有现代C ++编译器中,以下是合法的:

std::array<float, 4> a = {1, 2, 3, 4};

我正在尝试创建具有类似构造语义的我自己的类,但我遇到了一个恼人的问题。考虑以下尝试:

#include <array>
#include <cstddef>

template<std::size_t n>
class float_vec
{
private:
  std::array<float, n> underlying_array;

public:
  template<typename... Types>
  float_vec(Types... args)
    : underlying_array{{args...}}
  {
  }
};

int main()
{
  float_vec<4> v = {1, 2, 3, 4}; // error here
}

当使用上面的int文字时,编译器会抱怨它不能隐式地将int转换为float。我认为它在std::array示例中有效,因为给出的值是已知在float域内的编译时常量。另一方面,可变参数模板使用int作为参数类型,转换发生在构造函数的初始化列表中,其中值在编译时是未知的。

我不想在构造函数中进行显式强制转换,因为即使它们不能由float表示,它也会允许所有数值。

我能想到得到我想要的唯一方法是以某种方式具有可变数量的参数,但具有特定类型(在这种情况下,我想要float)。我知道std::initializer_list,但我希望能够在编译时强制执行参数的数量。

有什么想法吗?我想要的C ++ 11甚至可能吗?为C ++ 14提出的新解决方案是什么?

4 个答案:

答案 0 :(得分:3)

您看到的第一个是default aggregate initialization。它已经存在于最早的K&amp; R C之后。如果您的类型是聚合,它已经支持聚合初始化。此外,您的示例很可能会编译,但初始化它的正确方法是std::array<int, 3> x ={{1, 2, 3}};(注意双括号)。

C ++ 11中添加的是initializer_list构造,需要一些编译器魔法才能实现。

因此,您现在可以做的是添加接受std::initializer_list值的构造函数和赋值运算符,这将为您的类型提供相同的语法。

示例:

#include <initializer_list>

struct X {
  X(std::initializer_list<int>) {
    // do stuff
  }
};

int main()
{
  X x = {1, 2, 3};

  return 0;
}

为什么您目前的做法不起作用?因为在C ++ 11中std::initializer_list::size不是constexprinitializer_list类型的一部分。您不能将其用作模板参数。

一些可能的黑客攻击:使你的类型成为聚合。

#include <array>

template<std::size_t N>
struct X {
  std::array<int, N> x;
};

int main()
{
  X<3> x = {{{1, 2, 3}}}; // triple braces required
  return 0;
}

提供make_*函数来推断出参数的数量:

#include <array>

template<std::size_t N>
struct X {
  std::array<int, N> x;
};

template<typename... T>
auto make_X(T... args) -> X<sizeof...(T)>
// might want to find the common_type of the argument pack as well
{ return X<sizeof...(T)>{{{args...}}}; } 

int main()
{
  auto x = make_X(1, 2, 3);
  return 0;
}

答案 1 :(得分:3)

一个小技巧是使用构造函数继承。只需让你的类派生自另一个具有一包你想要的参数的类。

template <class T, std::size_t N, class Seq = repeat_types<N, T>>
struct _array_impl;

template <class T, std::size_t N, class... Seq>
struct _array_impl<T, N, type_sequence<Seq...>>
{
    _array_impl(Seq... elements) : _data{elements...} {}
    const T& operator[](std::size_t i) const { return _data[i]; }

    T _data[N];
};


template <class T, std::size_t N>
struct array : _array_impl<T, N>
{
    using _array_impl<T, N>::_array_impl;
};

int main() {
    array<float, 4> a {1, 2, 3, 4};
    for (int i = 0; i < 4; i++)
        std::cout << a[i] << std::endl;
    return 0;
}

以下是repeat_types实用程序的示例实现。此示例使用对数模板递归,与线性递归相比,它实现起来不太直观。

template <class... T>
struct type_sequence
{
    static constexpr inline std::size_t size() noexcept { return sizeof...(T); }
};


template <class, class>
struct _concatenate_sequences_impl;
template <class... T, class... U>
struct _concatenate_sequences_impl<type_sequence<T...>, type_sequence<U...>>
    { using type = type_sequence<T..., U...>; };
template <class T, class U>
using concatenate_sequences = typename _concatenate_sequences_impl<T, U>::type;


template <std::size_t N, class T>
struct _repeat_sequence_impl
    { using type = concatenate_sequences<
        typename _repeat_sequence_impl<N/2, T>::type,
        typename _repeat_sequence_impl<N - N/2, T>::type>; };
template <class T>
struct _repeat_sequence_impl<1, T>
    { using type = T; };
template <class... T>
struct _repeat_sequence_impl<0, type_sequence<T...>>
    { using type = type_sequence<>; };
template <std::size_t N, class... T>
using repeat_types = typename _repeat_sequence_impl<N, type_sequence<T...>>::type;

答案 2 :(得分:1)

如果使用多个大括号来初始化实例,则可以利用另一种类型的list-init来接受这些转换以获得编译时常量。这是一个使用原始数组的版本,因此您只需要用于构造的parens +大括号:

#include <array>
#include <cstddef>

template<int... Is> struct seq {};
template<int N, int... Is> struct gen_seq : gen_seq<N-1, N-1, Is...> {};
template<int... Is> struct gen_seq<0, Is...> : seq<Is...> {};

template<std::size_t n>
class float_vec
{
private:
  std::array<float, n> underlying_array;

  template<int... Is>
  constexpr float_vec(float const(&arr)[n], seq<Is...>)
    : underlying_array{{arr[Is]...}}
  {}

public:
  constexpr float_vec(float const(&arr)[n])
    : float_vec(arr, gen_seq<n>{})
  {}
};

int main()
{
  float_vec<4> v0 ({1, 2, 3, 4});   // fine
  float_vec<4> v1 {{1, 2, 3, 4}};   // fine
  float_vec<4> v2 = {{1, 2, 3, 4}}; // fine
}

答案 3 :(得分:-1)

明确指定初始化为浮点类型的数据类型。您可以通过执行“1.0f”而不是“1”来完成此操作。如果是双倍,则输入“1.0d”。如果它很长,则放“1l”,对于unsigned long放“1ul”等等。

我在这里测试了它:http://coliru.stacked-crooked.com/a/396f5d418cbd3f14 在这里:http://ideone.com/ZLiMhg

你的代码很好。你刚刚初始化了这个类有点不正确。

#include <array>
#include <cstddef>
#include <iostream>

template<std::size_t n>
class float_vec
{
private:
  std::array<float, n> underlying_array;

public:
  template<typename... Types>
  float_vec(Types... args)
    : underlying_array{{args...}}
  {
  }

  float get(int index) {return underlying_array[index];}
};

int main()
{
  float_vec<4> v = {1.5f, 2.0f, 3.0f, 4.5f}; //works fine now..

  for (int i = 0; i < 4; ++i)
    std::cout<<v.get(i)<<" ";
}