首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >在模板构建器模式中分解重复的构造函数调用

在模板构建器模式中分解重复的构造函数调用
EN

Stack Overflow用户
提问于 2018-05-26 13:53:50
回答 3查看 292关注 0票数 14

考虑下面的builder-like类,它最终允许我构造一个具有成员变量的特定(运行时)值的对象,以及嵌入一些由几个(编译时)类型携带的行为。

相同的构建允许更新成员变量(通常的构建器模式),以及更改与构建器的类型携带状态相关联的模板类型参数(仅显示了几个模板类型参数和成员,但实际上会有更多):

代码语言:javascript
复制
template <typename T1 = DefaultT1, typename T2 = DefaultT2>
class Builder {
  int param1, param2;
  Builder(int param1, int param2) : param1{param1}, param2{param2} {}
public:
  Builder() : Builder(default1, default2) {}

  // methods to change param1 and param2 not shown

  /* return a new Builder with T1 changed to the given T1_NEW */
  template <typename T1_NEW>
  Builder<T1_NEW, T2   > withT1() { return {param1, param2}; }

  template <typename T2_NEW>
  Builder<T1   , T2_NEW> withT2() { return {param1, param2}; }

  Foo make() {
    // uses T1 and T2 to populate members of foo
    return Foo{ typename T1::member, typename T2::another };
  }
};

请注意withT1<>withT2<>方法,它们允许您分别为T1T2返回具有不同类型的新构建器。这些方法的主体是相同的:return {param1, param2};,并且在实践中比这里显示的要复杂得多(例如,如果有很多参数)。

我想把body分解成一些构造的方法,比如:

代码语言:javascript
复制
template <typename T1_, typename T2_>
Builder<T1_, T2_> copy() { return {param1, param2}; }

然后,每个withT*方法都可以直接调用copy。

但是,我不清楚如何避免在调用中包含完全限定的Builder类型:

代码语言:javascript
复制
template <typename T1_NEW>
Builder<T1_NEW, T2   > withT1() { return copy<T1_NEW, T2>(); }

这里的解决方案比原始毒药更糟糕,因为我需要用<T1_NEW, T2>来限定每个复制调用(对于每个withT*方法,这是不同的)。有没有什么方法可以引用返回类型或另一种类型的演绎,我可以在每个函数中以相同的方式调用copy()

我是用C++11编写的,但也欢迎讨论C++11解决方案如何在以后的标准中得到改进。

EN

回答 3

Stack Overflow用户

发布于 2018-05-29 19:40:47

您可以引入一个具有隐式转换的构建器代理,以省去一些输入:

代码语言:javascript
复制
template<typename T1, typename T2>
struct Builder;

struct BuilderProxy
{
    int param1, param2;

    template<typename T1, typename T2>
    operator Builder<T1, T2>() const { return {param1, param2}; }
};

template <typename T1, typename T2>
struct Builder {
    int param1, param2;
    Builder(int param1, int param2) : param1{param1}, param2{param2} {}

    BuilderProxy copy() { return {param1, param2}; }

    template <typename T1_NEW>
    Builder<T1_NEW, T2   > withT1() { return copy(); }

    template <typename T2_NEW>
    Builder<T1   , T2_NEW> withT2() { return copy(); }
};

int main() {
    Builder<int, int> a(1, 2);
    Builder<double, double> b = a.withT1<double>().withT2<double>();
}
票数 8
EN

Stack Overflow用户

发布于 2018-05-30 01:56:31

我没有针对C++11的解决方案,但正如您自己所说,C++14可能对其他人有帮助。

如果我没理解错的话,您需要一个存储任意参数的类,并以一种方便的方式将所有参数传递给构造函数。这可以使用可变模板参数和std::tuple来实现

代码语言:javascript
复制
#include <tuple>

template <typename... Args>
class Builder
{
public:
    explicit Builder(Args... args)
        : arg_tuple(std::forward<Args>(args)...)
    {}

    template <typename T>
    T make()
    {
        return std::make_from_tuple<T>(arg_tuple);
    }

    template <typename T>
    Builder<Args..., T> with(T t)
    {
        return std::make_from_tuple<Builder<Args..., T>>(std::tuple_cat(arg_tuple, std::make_tuple(std::move(t))));
    }

private:
    std::tuple<Args...> arg_tuple;
};

template <typename... Args>
Builder<Args...> make_builder(Args... args)
{
    return Builder<Args...>(std::forward<Args>(args)...);
}

用法:

代码语言:javascript
复制
struct Foo
{
    Foo(int x, int y)
        : x(x), y(y)
    {}
    int x;
    int y;
};

struct Bar
{
    Bar(int x, int y, float a)
        : x(x), y(y), a(a)
    {}
    int x;
    int y;
    float a;
};

int main()
{
    auto b = make_builder().with(5).with(6);
    auto foo = b.make<Foo>();  // Returns Foo(5, 6).
    auto b2 = b.with(10.f);
    auto bar = b2.make<Bar>();  // Returns Bar(5, 6, 10.f).
}

虽然std::make_from_tuple是C++17,但它可以使用C++14功能来实现:

代码语言:javascript
复制
namespace detail
{
    template <typename T, typename Tuple, std::size_t... I>
    constexpr T make_from_tuple_impl(Tuple&& t, std::index_sequence<I...>)
    {
        return T(std::get<I>(std::forward<Tuple>(t))...);
    }
}

template <typename T, typename Tuple>
constexpr T make_from_tuple(Tuple&& t)
{
    return detail::make_from_tuple_impl<T>(
        std::forward<Tuple>(t),
        std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
}
票数 8
EN

Stack Overflow用户

发布于 2018-05-29 19:21:00

我不确定我100%理解你的问题。让我试试性地回答一下:我在您的代码片段中添加了一个模板化的隐式转换操作符,该操作符调用reinterpret_cast

代码语言:javascript
复制
template<typename U1, typename U2>
operator Builder<U1, U2>(){ return *reinterpret_cast<Builder<U1, U2>*>(this); }

这通常很麻烦,也不安全,但在您的情况下,它可以完成工作。它允许withT1和withT2成员函数类似于

代码语言:javascript
复制
template <typename T1_NEW>
Builder<T1_NEW, T2   > withT1() { return *this; }

template <typename T2_NEW>
Builder<T1   , T2_NEW> withT2() { return *this; }

下面是我用来测试代码的代码片段

代码语言:javascript
复制
#include <type_traits>

template<typename T1, typename T2>
struct Foo;

template<>
struct Foo<int, double>{
    int mInt;
    double mDouble;
};

template<>
struct Foo<char, double>{
    char mChar;
    double mDouble;
};

struct t1{
    using type = int;
    static type member;
};

struct t2{
    using type = double;
    static type another;
};

struct tt1{
    using type = char;
    static type member;
};

template <typename T1 = t1, typename T2 = t2>
class Builder {
  // int param1, param2;
  Builder(int param1, int param2) : param1{param1}, param2{param2} {}
public:
  int param1, param2;
  Builder() : Builder(0, 0) {}

  template<typename U1, typename U2>
  operator Builder<U1, U2>(){ return *reinterpret_cast<Builder<U1, U2>*>(this); }

  template <typename T1_NEW>
  Builder<T1_NEW, T2   > withT1() { return *this; }

  template <typename T2_NEW>
  Builder<T1   , T2_NEW> withT2() { return *this; }

  Foo<typename T1::type, typename T2::type> make() {
    // uses T1 and T2 to populate members of foo
    return Foo<typename T1::type, typename T2::type>{T1::member, T2::another};
  }
};


int main(){
Builder<t1, t2> b;
auto c = b.withT1<tt1>();
static_assert(std::is_same<decltype(c), Builder<tt1, t2>>::value, "error");
}
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50539774

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档