2

为了在解决复杂的 PDE 系统时让生活变得更轻松,我正在编写一个 C++ 包装器,围绕 C 数值库(的相关部分)。在处理多个未知数时,该库只需为每个网格点分配一个数组,并将其指针传递给用户指定的函数。用户可以通过 F[0], F[1], ... 引用每个未知数

当然,这些未知数通常有正确的数学名称,如果能够这样称呼它们就好了。一个明显的解决方案是定义一个structlike

template <typename T>
struct unknowns
{
    T a;
    T b;
    T c;
    T d;
};

并将 转换double*unknowns<double>*using reinterpret_cast。这似乎确实有效,但是在阅读后我可以将结构视为数组吗?我一直在尝试找到一个更好的解决方案来保证这个转换过程的正确性(并且还可以优雅地处理非标量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> Fvia

    auto F = factory<4>::wrap_array<unknowns>(f);

这是相当不错的。

现在更好的是,如果我也可以省略<4>,但是我无法弄清楚如何做到这一点。大概应该可以使用 SFINAE 来确定该结构拥有多少个成员,但std::is_constructible似乎无法满足我的需要。

谁能建议这怎么可能?

4

2 回答 2

1

您的问题是在编译时确定模板中的成员数struct Target<typename T>,其中Target的 PODstruct模板的成员只知道由 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>,我们可以编写递归 SFINAE解决方案。编译时递归由于替换失败而从该限制降低,该替换失败基于表达式的不可满足类型,该表达式尝试Target<T>使用太多T 类型的初始化器来初始化 a: 直到替换没有失败,然后 - 由于 POD 字符Target<T>- 我们知道它的字段数是它最终接受的初始化列表的长度。我们不需要关心这些推测初始化尝试了哪些值,只要这些值是可转换为 T 的类型。

(当然,我们不能只从 0个初始化器向上Target<T>递归,因为 它会接受任何不太长的初始化器列表

为了实现这样的解决方案,我们需要一种编译时方法来生成I任意长度类型的初始化列表,其中I可转换为T. 如果 I可以是整数类型,那么用于 编译时生成整数序列的现有技术的各种示例将浮现在脑海中(包括来自 SO C++ 杰出人物的示例: ShaubWakeleyKühll)。

具有这种低电阻线的障碍是它显然需要T整数类型构造的约束I。这不会排除非标量T,但它会非常缩小范围。

然而,障碍只是显而易见的。因为我们不关心T我们的推测初始化列表是由什么组成的,所以它们最好都是相同T的,如果我们只规定它们T是可默认构造的,那么生成相同的列表就不会有困难。然后要构建这些初始化列表,我们实际上不需要T从整数类型构造I。我们只需T要从某个可以从Sthat 构造的中间类型进行构造I。我们可以简单地从模板创建这样的一个,比如说,for ,具有所需的属性, 即构造函数并返回。Sshim<U>U = Tshim<U>(I)shim<U> operator T() constU()

到现在为止还挺好。但是现在有一个更通用的解决方案吗?

T 我们有一种方法可以找到将接受的 intializer-list-of- 的最大长度,因此可以根据我们对其字符的先决条件Target<T>推断模板中的字段数。Target假设我们放弃了这些先决条件:那Target是一个模板Target<T>;它的所有字段都是类型T;它是 POD。

然后,我们仍然会考虑一种编译方法来确定是否可以使用任何 长度 <= 任意限制的Target初始化列表来构造任何类型。这可能比初步想法更有用(尽管仍然足够recheche)。TM

这种额外的普遍性的一个微不足道的成本是模板解决方案的接口不能再简单地Target根据您的问题在模板打开TTarget 进行参数化。T在这种情况下,它必须由 Target<T>和参数化T。一个更重要的惩罚是,我们现在需要另外参数化模板接口,其中=应寻求M初始化列表的限制长度。Target为什么?因为 ifTarget不是 POD,then不再是可能接受sizeof(Target)的初始化器数量的上限。Target

对这样的需求M正是您不喜欢自己的解决方案的地方。但是更通用的解决方案仍然可以避免在PODTarget 对它的需要 。由于该属性可被 检测到 ,因此 在这种情况下,std::is_pod<Target>::value == true更通用的解决方案可以默认为,否则根本不默认。Msizeof(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
于 2013-06-22T19:08:25.587 回答
0

另一个包装器怎么样:

template <typename T, unsigned int N>
Target<T> wrap_actual_array(T const (&a)[N])
{
    return factory<N>::wrap_array<unkowns>(a);
}
于 2013-06-06T15:25:52.877 回答