【问题标题】:How to determine if a struct can be constructed from a given initializer list?如何确定是否可以从给定的初始值设定项列表构造结构?
【发布时间】:2013-06-06 15:17:35
【问题描述】:

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

当然,这些未知数通常有正确的数学名称,如果能够这样引用它们会很好。一个明显的解决方案是定义一个struct like

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

并使用reinterpret_castdouble* 转换为unknowns&lt;double&gt;*。这似乎确实有效,但是在阅读Can I treat a struct like an array? 之后,我一直在尝试找到一个更好的解决方案,以保证此转换过程的正确性(并且还可以优雅地处理非标量T - 我稍后可能需要一些东西)。

那么,第二个最明显的解决方案是重新定义vars,使其拥有T&amp;,然后编造如下内容

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&lt;double&gt; F

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

相当不错。

现在更好的是如果我也可以省略&lt;4&gt;,但是我不知道如何做到这一点。大概应该可以使用 SFINAE 来确定该结构拥有多少成员,但 std::is_constructible 似乎无法满足我的需求。

谁能建议这怎么可能?

【问题讨论】:

  • 旁注:一年之内我们应该得到概念精简版来解决这个问题
  • 如何省略“4”?那条线上还有谁知道值“4”?
  • @Kerrek SB 在这种情况下,由于unknowns 有四个成员,它只能从一个正好有 4 个成员的初始化列表构造。其他任何事情都会失败。肯定有办法利用它并让编译器自动推断出&lt;4&gt;
  • 哦,现在我明白你的意思了。有趣...
  • 我也遇到了同样的问题。那时,我定义了一个枚举,包含数学名称。例如enum { X, Y, Z, SIZE};,然后给定一个数组v,我会输入v[Y] 而不是v[1],等等。我很好奇你得到什么答案,投票赞成你的问题!

标签: c++ c++11 template-meta-programming initializer-list


【解决方案1】:

您的问题是在编译时确定成员的数量 在模板struct Target&lt;typename T&gt; 中,其中Target 是 一个 POD struct 模板,其成员只知道由 0 个或多个 T 组成, T 可以满足您的即时需求,double

您想要一个与 T 甚至不是标量一致的解决方案。

这可以通过利用以下事实来解决,但有一个小限制: struct Target&lt;T&gt; 的成员数不能多于 sizeof(Target&lt;T&gt;)。它是 确实,包含位域的struct X 可能有更多的成员 sizeof(X),但是位域的类型{unsigned|signed} int, 所以该语句适用于struct Target&lt;T&gt;,适用于任何T

次要限制是T 是默认可构造的。

由于sizeof(Target&lt;T&gt;) 是字段数的编译时上限 在Target&lt;T&gt; 中,我们可以编写一个recursive-SFINAE 解决方案。 编译时递归因替换失败而从该限制降低 基于试图 用 T 类型的 太多初始化器 初始化 Target&lt;T&gt;: 直到 替换不会失败,然后 - 感谢Target&lt;T&gt; 的 POD 字符 - 我们知道它的字段数就是初始化器的长度 它最终接受的列表。我们不需要关心这些值是什么 尝试进行推测性初始化,前提是值是 类型可转换为 T。

(当然,我们不能只从 0 个初始化器递归 up,因为 Target&lt;T&gt; 将接受任何太长的初始化列表。)

要实现这样的解决方案,我们需要一种生成初始化程序的编译时方法 任意长度的I 类型列表,其中I 可转换为T。如果 I 可以是整数类型,然后是现有技术的各种示例 整数序列的编译时生成会浮现在脑海中(包括 来自 SO C++ 名人的示例: Shaub,Wakeley, Kühll)。

这种低电阻线的障碍显然是它的约束 意味着T 可以从integral 类型I 构造。那不会 排除非标量 T,但它会非常缩小范围。

然而,障碍只是显而易见的。因为我们不在乎Ts 写什么 我们的推测初始化器列表,它们可能都是相同的T,并且 如果我们只规定T 是默认可构造的,则不会有 很难生产出相同的产品。然后构建这些初始化列表 我们实际上不需要T 可以从整数类型I 构造。 我们只需要T 可以从一些中间类型S 构造,即 可以从 I 构造。我们可以简单地创建这样的S 从 模板,例如shim&lt;U&gt;,用于U = T,具有shim&lt;U&gt;(I) 所需的属性 是一个构造函数,shim&lt;U&gt; operator T() const 返回U()

到目前为止一切顺利。但现在是否有更通用的解决方案?

我们有办法找到 intializer-list-of-T 的最大长度 Target&lt;T&gt; 将接受,因此可以推断出 模板Target 给出了我们对其特征的先决条件。假设我们 放弃了这些先决条件:Target 是模板Target&lt;T&gt;;那 它的所有字段都是T 类型;它是 POD。

然后我们仍然会考虑一个编译方法来确定 任何类型 Target 是否能够用任何 initializer-list-of-T 构造 长度 M。这可能比 初步的想法(虽然仍然足够recheche)。

这种额外通用性的一个微不足道的成本是 模板解决方案不能再简单地通过TargetT 参数化 每当Target T 上的模板时,根据您的问题。在这种情况下,它将 必须由Target&lt;T&gt;T 参数化。更重要的惩罚 将是我们现在需要另外参数化 模板接口与M = 限制长度,其中 应寻找Target 的初始化列表。为什么?因为如果Target 不是 POD,然后sizeof(Target) 不再是数字的上限 Target 可能接受的初始化器数量。

需要这样一个M 正是您不喜欢自己的解决方案的地方。 但是更通用的解决方案仍然可以避免任何时候对它的需要 Target POD。由于该属性可以通过 std::is_pod&lt;Target&gt;::value == true,更通用的解决方案可以默认M 在这种情况下,sizeof(Target),否则根本不默认。

以下解决方案是这一切的残余。对于我的编译时整数序列 我选择抄袭 C++ 标准委员会成员的仪器 Daniel Krügler,

ma​​ke_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

【讨论】:

  • 我最终自己写了一些与此非常相似的东西,但编译器不会接受它。我还没有完全弄清楚你的解决方案和我的解决方案有什么区别,但我认为这可能归结为英特尔相对缺乏 C++11 支持。当我弄清楚更多时会报告。非常感谢!
【解决方案2】:

另一个包装器怎么样:

template <typename T, unsigned int N>
Target<T> wrap_actual_array(T const (&a)[N])
{
    return factory<N>::wrap_array<unkowns>(a);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-29
    • 2012-07-02
    相关资源
    最近更新 更多