【问题标题】:Define a constructor for a class whose members exist according to the template base classes根据模板基类,为其成员存在的类定义构造函数
【发布时间】:2017-02-11 23:13:16
【问题描述】:

我正在研究一个类来识别纹理中的图像,如下所示:

using level_t = ...
using layer_t = ...
using face_t = ...

template <bool>
struct levels {};

template <>
struct levels<true> {
    level_t level = level_t{0};
};

template <bool>
struct layers {};

template <>
struct layers<true> {
    layer_t layer = layer_t{0};
};

template <bool>
struct faces {};

template <>
struct faces<true> {
    face_t face = face_t{0};
};

template <bool HasLevels, bool HasLayers, bool HasFaces>
struct index : levels<HasLevels>, layers<HasLayers>, faces<HasFaces> {};

level_tlayer_tface_t 是 int 的一种“强类型定义”(基本上是不同的类型,看起来像 int 但没有隐式转换)。

现在我可以像这样使用我的类型了:

using Index1 = index<true, true, false>;
void f(Index1);

Index1 v;
v.level = level_t{1};
v.layer = layer_t[1};
f(v);

但为了让我的用户的生活更轻松,我想允许此代码:

f({level_t{1}, layer_t{1}};

由于基类,聚合初始化已经失效,所以我需要自己编写一个构造函数;编写支持所有参数组合(但不支持重新排序)的构造函数的最佳/最智能方法是什么?

【问题讨论】:

  • 是否有理由必须使用多重继承而不是聚合?
  • all the argument combinations (but not the reorder) 是什么意思?您是否在没有重新排序构造函数参数的情况下引用任何模板参数值?
  • @zneak 在这种情况下是的,根据index 类型的“配置”方式,某些属性必须消失,尝试使用它们必须导致编译错误
  • @PawełStankowski given index&lt;true, false, true&gt; 我想要一个接受 (level_t, face_t) 但不接受 (face_t, level_t) 的构造函数

标签: c++ c++11 c++14


【解决方案1】:

如果您传递错误的类型或参数数量,错误消息会有点糟糕,所以我不确定这是否值得,但是... p>

需要一种方法来过滤和随机访问一组类型;实际上,我会为此使用BrigandBoost.Hana,但我会在这里坚持使用std::tuple&lt;&gt; 来保持这个标准:

namespace detail {
    template<template<bool> class Holder, bool B>
    std::integral_constant<bool, B> holder_value_(Holder<B>);

    template<typename HolderT>
    constexpr decltype(detail::holder_value_(std::declval<HolderT>())) holder_value() {
        return {};
    }

    template<typename UnfilteredT, typename FilteredT>
    struct filter_holders;

    template<typename UnfilteredT, typename FilteredT = std::tuple<>>
    using filter_holders_t = typename filter_holders<UnfilteredT, FilteredT>::type;

    template<typename... Fs>
    struct filter_holders<std::tuple<>, std::tuple<Fs...>> {
        using type = std::tuple<Fs...>;
    };

    template<typename U, typename... Us, typename... Fs>
    struct filter_holders<std::tuple<U, Us...>, std::tuple<Fs...>> : filter_holders<
        std::tuple<Us...>,
        std::conditional_t<holder_value<U>(), std::tuple<Fs..., U>, std::tuple<Fs...>>
    > { };
}

需要的另一件事是找到一个'holder'-type正在包装的intlike-type(例如levels&lt;true&gt;包装level_t等)。这可以非侵入性地完成,但在这里我假设一个内部的value_type typedef。

有了这些就相当简单了——我们得到一个模板参数为true的基类型包,并编写一个构造函数,其初始化器只是该包的扩展:

namespace detail {
    template<typename... HolderTs>
    std::tuple<typename HolderTs::value_type...> value_types_(std::tuple<HolderTs...>);

    template<typename HoldersT>
    using value_types_t = decltype(detail::value_types_(std::declval<HoldersT>()));
}

template<bool HasLevels, bool HasLayers, bool HasFaces>
struct index : levels<HasLevels>, layers<HasLayers>, faces<HasFaces> {
private:
    using bases_t = std::tuple<levels<HasLevels>, layers<HasLayers>, faces<HasFaces>>;
    using true_bases_t = detail::filter_holders_t<bases_t>;
    using arg_types_t = detail::value_types_t<true_bases_t>;

    template<std::size_t... Is, typename... ArgTs>
    index(std::index_sequence<Is...>, ArgTs&&... args)
    : std::tuple_element_t<Is, true_bases_t>{std::forward<ArgTs>(args)}... { }

public:
    index() = default;

    template<
        typename... ArgTs,
        std::size_t S = sizeof...(ArgTs),
        typename = std::enable_if_t<
            S && std::is_same<std::tuple<std::decay_t<ArgTs>...>, arg_types_t>{}
        >
    >
    index(ArgTs&&... args)
    : index{std::make_index_sequence<S>{}, std::forward<ArgTs>(args)...} { }
};

Online Demo

编辑:更新了更严格的实现,不允许可转换但不相同的参数类型,并且不会错误地干扰复制/移动构造。

【讨论】:

  • 我已经在使用 Boost.Hana,所以我正在更改您的代码以使用它。从没想过可以使用元函数(std::tuple_element_t)作为基类初始化器,哇 :)
  • @dvd :是的,base-initializer 只需要以某种方式命名 base,但不一定直接;所以decltype、简单的类型定义、特征/元函数等都是可行的选择。我很高兴你已经在使用 Hana; IMO 它很容易在这里实现最干净的实现,它只是一个很难在没有 3 页长答案的情况下在 SO 答案的上下文中出售的库。 ;-]
  • @dvd :(我不确定问题作者是否收到有关答案编辑的通知...)更新了index 的复制/移动构造函数的重要修复。
【解决方案2】:

这是一个允许代码的演示代码。

#include <iostream>
#include <type_traits>

struct A {
  A() = default;
  A(const A &) {
    std::cout << "A" << std::endl;
  }
};

struct B {
  B() = default;
  B(const B &) {
    std::cout << "B" << std::endl;
  }
};

struct C {
  C() = default;
  C(const C &) {
    std::cout << "C" << std::endl;
  }
};

template <class... Base>
struct Hyper : public Base... {
  template <class... Args>
  Hyper(Args&&... args) : Base(std::forward<Args>(args))... {}
};

template <class... Base>
auto factory(Base&&... args) {
  return Hyper<std::decay_t<Base>...>(std::forward<Base>(args)...);
}

int main() {
  auto obj = factory(A{}, B{}, C{});
  return 0;
}

Hyper 的所有基类都是复制/移动构造的。

一些static_assert 可以添加到factory 以防止它生成任何怪异的类。

正如评论所说,这不是一个好主意。如果你做对了,一切都会好起来的,但如果你做错了,即使是一个小错误,模板+多继承都可能是你的噩梦。 (您最终可能会收到 100000 行编译器错误消息。)

【讨论】:

  • 为了防止时髦的类,你可以定义一个类型特征template&lt;typename T&gt; using is_valid_base = std::integral_constant&lt;bool, std::is_same&lt;T, A&gt;::value || std::is_same&lt;T, B&gt;::value || std::is_same&lt;T, C&gt;::value&gt;;
猜你喜欢
  • 1970-01-01
  • 2017-03-14
  • 1970-01-01
  • 1970-01-01
  • 2019-03-24
  • 1970-01-01
  • 2017-11-03
  • 1970-01-01
  • 2013-05-07
相关资源
最近更新 更多