为了在解决复杂的PDE系统时让生活更轻松,我正在编写一个C ++包装器(C相关部分)C数值库。处理多个未知数时,库只为每个网格点分配一个数组,并将其指针传递给用户指定的函数。用户可以通过F [0],F [1],...
来引用每个未知数当然,这些未知数通常都有正确的数学名称,能够像这样引用它们会很好。一个明显的解决方案是定义struct
喜欢
template <typename T>
struct unknowns
{
T a;
T b;
T c;
T d;
};
并使用double*
将unknowns<double>*
转换为reinterpret_cast
。这确实有效,但是在阅读Can I treat a struct like an array?之后,我一直在努力找到一个更好的解决方案来保证这个转换过程的正确性(并且还优雅地处理非标量T
- 我可能会需要一点时间。)
然后,第二个最明显的解决方案是重新定义vars
,使其保持T&
,然后编制类似下面的内容
template <size_t DOF>
class factory
{
private:
template <template <typename> class Target, typename T, typename... Index>
static typename std::enable_if<(sizeof...(Index) < DOF), Target<T>>::type
_do_construct(T* array, Index... index)
{
return _do_construct<Target>(array, index..., sizeof...(Index));
}
template <template <typename> class Target, typename T, typename... Index>
static typename std::enable_if<sizeof...(Index) == DOF, Target<T>>::type
_do_construct(T* array, Index... index)
{
return { array[index]... };
}
public:
template <template <typename> class Target, typename T>
static Target<T> wrap_array(T* array)
{
return _do_construct<Target>(array);
}
};
有了这个,我现在可以通过
安全地将库提供的double* f
转换为unknowns<double> F
auto F = factory<4>::wrap_array<unknowns>(f);
这很不错。
现在更好的是,如果我也可以省略<4>
,但我无法弄清楚如何做到这一点。据推测,应该可以使用SFINAE来确定结构所拥有的成员数,但std::is_constructible
似乎无法满足我的需求。
有人可以建议如何做到这一点吗?
答案 0 :(得分:1)
您的问题是在编译时确定成员数量
在模板struct Target<typename T>
中,Target
所在的位置
一个POD struct
模板,其成员只知道包含0个或更多T
个,
以及T
满足您的直接需求double
。
您希望与T
一致的解决方案甚至不是标量。
通过利用以下事实,可以通过一个小的限制来解决这个问题:
struct Target<T>
成员不能超过sizeof(Target<T>)
。它是
确实,包含位域的struct X
可以拥有更多成员
sizeof(X)
,但位域的类型为{unsigned|signed} int
,
所以struct Target<T>
的声明适用于任何T
。
次要限制是T
可以默认构建。
由于sizeof(Target<T>)
是字段数的编译时上限
在Target<T>
中,我们可以编写 recursive-SFINAE 解决方案。
通过替换失败从该限制驱动编译时递归
基于试图表达的不可满足类型
使用类型为T的太多初始化程序初始化Target<T>
:直到
替换不会失败,然后 - 由于Target<T>
的POD特征 -
我们知道它的字段数是初始化程序的长度
它最终接受的清单。我们不需要关心这些价值观
如果值是a,则尝试推测初始化
类型可转换为T。
(当然,我们不能从0个initalizers中递归 up ,因为Target<T>
将接受任何不太长的初始化列表。)
要实现这样的解决方案,我们需要一个生成初始化程序的编译时方法
任意长度的I
类型的列表,其中I
可转换为T
。如果
I
可以是整体类型,然后是现有技术的各种示例
我们会想到编译时生成整数序列(包括
SO C ++名人的例子:
Shaub,Wakeley,
Kühll)。
这种低阻力线的障碍显然是它的制约因素
要求T
可以从整数类型I
构建。那不会
排除非标量T
,但它会非常缩小范围。
T
的构成
我们的推测初始化列表,它们可能都是相同的T
,和
如果我们只规定T
是默认可构造的,那么就没有
很难找出相同的。然后构建这些初始化列表
我们实际上不需要T
可以从整数类型I
构造。
我们只需要T
可以从某个中间类型S
构造
可以从I
构造出来。我们可以从a 创建这样的S
模板,例如,shim<U>
,U = T
,具有shim<U>(I)
所需的属性
是构造函数,shim<U> operator T() const
返回U()
。
到目前为止一切顺利。但是现在卡片上的解决方案更具普遍性吗?
我们有一种方法可以找到最大长度的intializer-list-of - T
Target<T>
将接受,因此可以推断出中的字段数量
模板Target
给出了我们的角色先决条件。假设我们
放弃了这些先决条件:Target
是模板Target<T>
;那
它的所有字段都是T
类型;它是POD。
然后我们仍然会看到一个确定的编译方法
是否有任何类型Target
能够使用任何初始化程序列表构建T
长度&lt; =任意限制M
。这可能比更有用
初步的想法(虽然仍然足够重新考虑)。
这种额外普遍性的一个微不足道的代价就是它的界面
模板解决方案无法再由Target
和T
简单参数化
根据您的问题,Target
T
上的模板时。在那种情况下它会
必须由Target<T>
和T
进行参数化。更重要的惩罚
将是我们现在需要另外参数化的事实
模板接口M
=限制长度a
应寻求Target
的初始化列表。为什么?因为Target
不是
POD,然后sizeof(Target)
不再是数字的上限
Target
可能接受的初始化程序。
需要这样的M
就是你不喜欢自己的解决方案。
但是更通用的解决方案仍然可以避免每次都需要它
Target
是 POD。由于该属性可被检测到
std::is_pod<Target>::value == true
,更通用的解决方案可以默认M
在这种情况下,sizeof(Target)
,否则不会默认它。
以下解决方案是所有这些的残留物。对于我的compiletime-integer-sequences 我选择剽窃C ++标准委员会成员 Daniel Krügler,
<强> make_indices.h 强>
#ifndef MAKE_INDICES_H
#define MAKE_INDICES_H
/* After [Daniel Krügler]
https://groups.google.com/forum/?fromgroups#!topic/comp.lang.c++.moderated/H6icuxL0NAY
*/
template<unsigned...> struct indices {};
namespace detail {
template<unsigned I, class Indices, unsigned N>
struct make_indices;
template<unsigned I, unsigned... Indices, unsigned N>
struct make_indices<I, indices<Indices...>, N>
{
typedef typename make_indices<I + 1, indices<Indices..., I>, N>::type type;
};
template<unsigned N, unsigned... Indices>
struct make_indices<N, indices<Indices...>, N>
{
typedef indices<Indices...> type;
};
} // namespace detail
template<unsigned N>
struct make_indices : detail::make_indices<0, indices<>, N> {};
#endif // EOF
然后我的贡献: initializer_count.h
#ifndef INITIALIZER_COUNT_H
#define INITIALIZER_COUNT_H
#include "make_indices.h"
#include <type_traits>
namespace detail {
/* class detail::shim<U> is a convenience wrapper of U whose
sole purpose is to be constructible from unsigned and
convertible to a U.
*/
template<typename U>
struct shim
{
static_assert(std::is_default_constructible<U>::value,
"U must be default-constructible for detail::shim<U>");
explicit shim(unsigned){};
operator U () const {
return U();
}
};
} // namespace detail
/*
class initializer_count<Target,T> will export
`static const unsigned value` == the maximum length <= Size of
initializer list of T that Target will accept.
Size defaults to sizeof(Target) if Target id POD. Otherwise a static_assert
is tripped if Size is defaulted.
*/
template<
class Target,
typename T,
unsigned Size = std::is_pod<Target>::value ? sizeof(Target) : unsigned(-1)
>
struct initializer_count;
// Terminal case
template<class Target, typename T>
struct initializer_count<Target,T,0>
{
static const unsigned value = 0;
};
// Recursion case.
template<class Target, typename T, unsigned Size>
struct initializer_count
{
static_assert(Size != unsigned(-1),
"Size cannot be defaulted for non-POD "
"Target in initializer_count<Target,T,Size>");
// SFINAE success. Target can be initialized with a list of length Size
template<unsigned ...I>
static constexpr auto count(indices<I...>) ->
decltype(Target{detail::shim<T>(I)...},Size) {
return Size;
}
// SFINAE failure.
template<unsigned ...I>
static constexpr unsigned count(...) {
// Recurse to Size - 1
return initializer_count<Target,T,Size - 1>::value;
}
static const unsigned value = count(typename make_indices<Size>::type());
};
#endif // EOF
测试程序(gcc 4.7.2 / 4.8.1,clang 3.2):
#include "initializer_count.h"
struct non_pod
{
non_pod(){}
non_pod(double a, short b)
: _a(a),_b(b){}
double _a = 42.0;
short _b = 42;
};
template <typename T>
struct five_unknowns
{
T a;
T b;
T c;
T d;
T e;
};
template <typename T>
struct one_unknown
{
T a;
};
template <typename T>
struct zero_unknowns {};
#include <iostream>
using namespace std;
int main()
{
static const unsigned initializer_max = 100;
static_assert(!std::is_pod<non_pod>::value,"");
cout << initializer_count<zero_unknowns<char>,char>::value << endl;
cout << initializer_count<one_unknown<int>,int>::value << endl;
cout << initializer_count<five_unknowns<double>,double>::value << endl;
// Need initializer_max for rest non-pod targets...
cout <<
initializer_count<five_unknowns<non_pod>,non_pod,initializer_max>::value
<< endl;
cout << initializer_count<non_pod,short,initializer_max>::value << endl;
return 0;
}
// EOF
预期产出:
0
1
5
5
2
答案 1 :(得分:0)
另一个包装器怎么样:
template <typename T, unsigned int N>
Target<T> wrap_actual_array(T const (&a)[N])
{
return factory<N>::wrap_array<unkowns>(a);
}