在我使用的所有现代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提出的新解决方案是什么?
答案 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
不是constexpr
或initializer_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)<<" ";
}