在接受用户定义文字的排列时防止过载爆炸

Ste*_*mer 3 c++ c++14

我有一个结构Dimensions,其中包含宽度和高度的模板参数:

template<unsigned W, unsigned H>
struct Dimensions
{
    static constexpr unsigned width = W;
    static constexpr unsigned height = H;
};
Run Code Online (Sandbox Code Playgroud)

我有WidthHeight类有各自值的模板参数:

template<unsigned N>
struct Width
{
    static constexpr unsigned value = N;
};

template<unsigned N>
struct Height
{
    static constexpr unsigned value = N;
};
Run Code Online (Sandbox Code Playgroud)

我已经为宽度和高度创建了用户定义的文字

template<char... cs>
constexpr auto operator""_w() -> Width<to_unsigned(0, parse(cs)...)>
{
    return {};
}

template<char... cs>
constexpr auto operator""_h() -> Height<to_unsigned(0, parse(cs)...)>
{
    return {};
}
Run Code Online (Sandbox Code Playgroud)

在哪里to_unsigned并将parse字符转换为无符号值

constexpr unsigned to_unsigned(unsigned p)
{
    return p;
}

template<class... Ts>
constexpr unsigned to_unsigned(unsigned val, unsigned v, Ts... vs)
{
    return to_unsigned(val * 10 + v, vs...);
}

constexpr unsigned parse(char C)
{
    return (C >= '0' && C <= '9')
        ? C - '0'
        : throw std::out_of_range("input is not a number");
}
Run Code Online (Sandbox Code Playgroud)

因此,我现在可以创建一个函数模板,它从字符串文字中获取WidthHeight实例,并返回一个Dimensions

template<unsigned W, unsigned H>
constexpr Dimensions<W, H> dimensions(Width<W>, Height<H>)
{
    return Dimensions<W, H>{};
}

auto d = dimensions(5_w, 10_h);

static_assert(d.width == 5, "");
static_assert(d.height == 10, "");
Run Code Online (Sandbox Code Playgroud)

我想允许用户只能提供一个维度(另一个是0),或者以不同的顺序提供文字.

我目前实现这一点的方法是有许多不同的重载:

// width, height
template<unsigned W, unsigned H = 0>
constexpr Dimensions<W, H> dimensions(Width<W>, Height<H>)
{
    return Dimensions<W, H>{};
}

// height, width
template<unsigned H, unsigned W = 0>
constexpr Dimensions<W, H> dimensions(Height<H>, Width<W>)
{
    return Dimensions<W, H>{};
}
Run Code Online (Sandbox Code Playgroud)

如果我现在添加第三个维度,过载Breadth的数量dimensions将会爆炸,因为我现在已经实现了它,每次可能的排列需要一个重载.

题:

有没有办法允许用户使用宽度,宽度和高度的任何组合/排序指定尺寸,省略一些等?

(工作实施例上coliru上面的代码在这里)

Bar*_*rry 8

最直接的方法是从元函数开始搜索特定维度:

template <template <unsigned> class Z, class... Ts>
struct find_dimension;

template <template <unsigned> class Z, class... Ts>
using find_dimension_t = typename find_dimension<Z, Ts...>::type;

// found it
template <template <unsigned> class Z, unsigned N, class... Ts>
struct find_dimension<Z, Z<N>, Ts...> {
    using type = Z<N>;
};

// keep going
template <template <unsigned> class Z, class T, class... Ts>
struct find_dimension<Z, T, Ts...> 
    : find_dimension<Z, Ts...>
{ };

// default to 0
template <template <unsigned> class Z>
struct find_dimension<Z>  {
    using type = Z<0>;
};
Run Code Online (Sandbox Code Playgroud)

然后我们就用它:

template <class... Dimensions>
auto dimensions(Dimensions... ) {
    using height = find_dimension_t<Height, Dimensions...>;
    using width = find_dimension_t<Width, Dimensions...>;
    using breadth = find_dimension_t<Breadth, Dimensions...>;

    return Dimensions<width::value, height::value, breadth::value>();
}
Run Code Online (Sandbox Code Playgroud)

更聪明的方法可能是利用继承:

template <template <unsigned> class Z, unsigned N>
constexpr unsigned get_dim(Z<N> d) { return N; }

template <template <unsigned> class Z>
constexpr unsigned get_dim(... ) { return 0; }

template<class... DimensionTs>
auto dimensions(DimensionTs...)
{
    struct X : DimensionTs... { };
    static constexpr X x {};

    return Dimensions<
        get_dim<Width>(x),
        get_dim<Height(x),
        get_dim<Breadth>(x)
    >();
}
Run Code Online (Sandbox Code Playgroud)

第二个很好的特性是重复的维度是一个编译错误.