Home > Mobile >  C varadic template function - only one instance of known parameter pack
C varadic template function - only one instance of known parameter pack

Time:12-22

I want to use a fold expression but the function will only ever be used with one known parameter pack.

i.e.

template <class... Types>
Foo fn_impl(Arg arg) {
  // here, a fold expression involving Types... that returns a Foo
}

Foo fn(Arg arg) {
    return fn_impl<Bar, Baz>(arg);
}

And that's it, fn_impl won't ever be used again. Is there a way to make this less verbose? Ideally, I'd like to write the implementation of fn in its body, without a separate implementation function (which adds noise, imo).

I know I could "unroll" the fold expression with the types in the parameter pack by hand, but in this case, using a fold expression is just very convenient to make the implementation of fn not too verbose.

Here's a complete example, see highlighted QUESTION comment:

#include <cassert>
#include <cstdio>
#include <memory>

struct Base {
  virtual ~Base() {}
  virtual const char *get_name() const = 0;
};

template <class Derived> struct Base_CRTP : public Base {
  const char *get_name() const final {
    return static_cast<const Derived *>(this)->name;
  }
};

struct A : Base_CRTP<A> {
  static constexpr const char *name = "A";
};
struct B : Base_CRTP<B> {
  static constexpr const char *name = "B";
};

#define ITest_DERIVED_CLASSES A, B

// QUESTION: Can this be entirely moved into the definition of #1?
template <class IType, class... Types>
std::unique_ptr<IType> make_by_class_index___impl(int class_index) {
  int i = 0;
  std::unique_ptr<IType> ret;
  ([&] {
    if (i   == class_index)
      ret = std::make_unique<Types>();
    return ret != nullptr;
  }() ||
   ...);
  return ret;
}

// #1
std::unique_ptr<Base> make_by_class_index(int class_index) {
  return make_by_class_index___impl<Base, ITest_DERIVED_CLASSES>(class_index);
}

template <class... Types> void print_pack_names() { (puts(Types::name), ...); }

int main() {

  print_pack_names<ITest_DERIVED_CLASSES>();

  puts("");

  auto p = make_by_class_index(0);
  assert(p != nullptr);
  printf("p name: %s\n", p->get_name());

  auto p2 = make_by_class_index(1);
  assert(p2 != nullptr);
  printf("p2 name: %s\n", p2->get_name());

  auto p3 = make_by_class_index(99);
  assert(p3 == nullptr);
}

CodePudding user response:

In lack of sufficient details, let's assume without loss of generality that Arg is int that that Foo, Bar and Baz are defined as follows:

struct Foo { int x; };
struct Bar { static constexpr int foo() { return 1; } };
struct Baz { static constexpr int foo() { return 2; } };

If you may use C 20 you can migrate a) a variadic function which contains a fold expression and b) a call to site to it, e.g.:

template <typename... Types> Foo fn_impl(int arg) {
  return { arg   (Types::foo()   ...) };
}

// at call site
fn_impl<Bar, Baz>(arg);

into a single generic immediately invoked lambda, leveraging that P0428R2 (introduced in C 20) allows template heads for generic lambdas:

Foo fn(int arg) {
  return []<typename... Types>(int arg) -> Foo {
    return { arg   (Types::foo()   ...) };
  }
  .operator()<Bar, Baz>(arg);
}

This arguably looks quite complex, though, particularly as you need to use the operator name syntax to provide explicit template arguments for the generic lambdas. The separate function approach is arguably easier to follow for future maintainers.

  • Related