4

I have a variadic data structure, each "layer" containing one field.

How can use all the fields stored in the structure as arguments to a function or a constructor?

template <class... Ts> class Builder {};

template <class T, class... Ts>
class Builder<T, Ts...> : public Builder<Ts...> {
public:
    Builder(T t, Ts... ts) : Builder<Ts...>(ts...), tail(t) {}

    Result build() {
      // want to use tail, Builder<Ts...>::tail, etc.
      // as ctor or function arguments without multiple specializations
    }

private:
    const T tail;
};

In general, I want to be capable of doing something like this:

Builder<int, string, int> b1{10, "aaa", 20};
Result r1 = b1.build(); // should invoke Result's constructor (int, string, int)

Builder<int> b2{10};
Result r2 = b2.build(); // should invoke Result's constructor (int)
max66
  • 65,235
  • 10
  • 71
  • 111
Adam Kotwasinski
  • 4,377
  • 3
  • 17
  • 40

5 Answers5

2

If you don't want to use tuple as a member to hold the values, you could do it this way:

template <class... Ts> class Builder {
  protected:
  template<class...Us>
  Result do_build(const Us&...us){
    return Result(us...);
    }
  };

template <class T, class... Ts>
class Builder<T, Ts...> : public Builder<Ts...> {
public:
    Builder(T t, Ts... ts) : Builder<Ts...>(ts...), tail(t) {}

    Result build() {
      return do_build();
    }
protected:
  template<class...Us>
  Result do_build(const Us&...us){
    return Builder<Ts...>::do_build(us...,tail);
    }
private:
    const T tail;
};
Oliv
  • 17,610
  • 1
  • 29
  • 72
1

One of the solutions I found is to pass intermediary tuple<...> that contains the fields and then unpack it using the mechanism described in "unpacking" a tuple to call a matching function pointer :

// unpacking helpers
template<int ...> struct seq {};

template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> {};

template<int ...S>
struct gens<0, S...> {
    typedef seq<S...> type;
};

// Builder with 0 fields returns an empty tuple
template <class... Ts> class Builder {
public:
    tuple<> compute_tuple() {
        return {};
    }
};

template <class T, class... Ts>
class Builder<T, Ts...> : public Builder<Ts...> {
public:
    Builder(T t, Ts... ts) : Builder<Ts...>(ts...), tail(t) {}

    Result build() {
        // get argument tuple
        auto arguments = compute_tuple();
        // use argument tuple as Result's argument
        return build_recursively(typename gens<1 + sizeof... (Ts)>::type{}, arguments);
    }

protected:
    // computing tuple - just join current element with superclass' result
    tuple<T, Ts...> compute_tuple() {
        const tuple<T> head{field};
        const tuple<Ts...> tail = Builder<Ts...>::compute_tuple();
        return tuple_cat(head, tail);
    }

private:
    template<int ...S>
    Result build_recursively(seq<S...>, tuple<T, Ts...> data) {
        // invoked matching Result's constructor
        return { std::get<S>(data) ... };
    }

    const T field;
};

Then it behaves properly:

Builder<string, string> b1{"a", "b"};
b1.build(); // invokes Result(string, string)

Still, maybe it's possible to do something simpler without that tuple intermediary?

Adam Kotwasinski
  • 4,377
  • 3
  • 17
  • 40
1

I suppose you can use a lambda (and save it in a std::function) to stock values.

Something as (caution: code not tested) (thanks to Oliv for a correction)

template <typename ... Ts>
class Builder
 {
   private:
      std::function<Result()>  fn;

   public:
      Builder (Ts const & ... ts) : fn{ [=]{ return Result{ts...}; }
       { }

      Result build ()
       { return fn(); }
 };
max66
  • 65,235
  • 10
  • 71
  • 111
  • 1
    Should not the ts... be captured? Then all these values are going to be stored on the heap, that is really expensive for such a small functionality. – Oliv Nov 14 '18 at 14:47
  • @Oliv - sure: `Builder (Ts & ... ts) : fn{ []{ return Result{ts...}; }`; but it's dangerous; if one construct `Builder` with a value that is destroyed before the `build()` calling, the corresponding saved reference is a dangling reference. Maybe it's possible to do something with perfect forwarding, but you have to templatize the constructor. – max66 Nov 14 '18 at 15:10
  • 1
    Your code is ill formed you must capture! And in order not to have UB parameters have to be captured by value. `[=]` – Oliv Nov 14 '18 at 19:31
  • @Oliv - so, it seems to me, is better to receive arguments as Ts const & ...; maybe, this way, some copy are avoided (but I suspect that the compilers optimize this aspect). – max66 Nov 14 '18 at 20:09
1
template <class... Ts>struct Builder {
  auto as_tie() const { return std::tie(); }
};

template <class T, class... Ts>
struct Builder<T, Ts...> : Builder<Ts...> {
  using base = Builder<Ts...>;
  auto as_tie()const{
    return std::tuple_cat( base::as_tie(), std::tie( tail ) );
  }

now Builder::as_tie() can be passed to std::apply (or backported version) or make_from_tuple.

Naturally the operator T trick can be used for return type deduction. But I'd usually advise against it.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

You can use an Idx<n> tag to get tail from n-th Builder:

template<std::size_t i> struct Idx {};

template<class... Ts>
class Builder {
public:
    void get_tail();
};

template <class T, class... Ts>
class Builder<T, Ts...> : public Builder<Ts...> {
private:
    static constexpr auto index = sizeof...(Ts);

public:
    Builder(T t, Ts... ts) : Builder<Ts...>(ts...), tail(t) {
    }

    Result build() {
        return build_impl(std::make_index_sequence<index + 1>{});
    }

protected:
    using Builder<Ts...>::get_tail;

    const T& get_tail(Idx<index>) {
        return tail;
    }

private:
    template<std::size_t... is>
    Result build_impl(std::index_sequence<is...>) {
        return Result{get_tail(Idx<index - is>{})...};
    }

private:
    const T tail;
};
Evg
  • 25,259
  • 5
  • 41
  • 83