0

I am trying to do something involving CTAD within function calls and it's best to first explain the desired behavior with an example, before asking my questions. NB: the below is a bit of a simplification of how I want the end thing to look.

namespace args {
template <typename...>
struct Required {};

template <typename...>
struct Optional {};


template <
  typename RequiredParameters, 
  typename OptionalParameters,
  typename... Params>
struct ParameterSet
{
  template <typename ...Args>
  constexpr ParameterSet(Args&&... args)
  : params_{std::forward_as_tuple(std::forward<Args>(args)...)} { }

  std::tuple<Params...> params_;
};
} // namespace args

template <typename... Params>
void foo(args::ParameterSet<Required<int, bool>, Optional<std::string>, Params...> params)
{
   // do stuff
}

int a = 5;
foo({true, a, std::string{"hello"}});

Summary of Requirements

So what I want from the stuff in namespace args is:

  • Be able to call the function with the arguments passed in any order.
  • Facilitate defining a set of parameters that are required and optional for a function call. I am planning to have type checks that there aren't any unexpected arguments, all required arguments are present, etc.
  • Embed those requirements in the function class signature, as opposed to a separate template parameter and using std::enable_if_t.
  • Be able to forward the value categories of the arguments within the storage tuple inside ParameterSet.
  • Support easy function calls with CATD being able to infer the whole type of ParameterSet, without needing to spell it out on each function call.
  • (missing here) The end thing I want is to be able to annotate each argument with a name, so something like foo(nameForBoolArg{true}, nameForIntArg{5}, ...). In essence what keyword / named arguments provide in python.

Things Tried

It was pretty clear to me I need some sort of deduction guide that helps the compiler. I tried the below:

template <typename RequiredParameters, typename OptionalParameters, typename... Args>
ParameterSet(Args&&...) -> ParameterSet<RequiredParameters, OptionalParameters, Args...>;

Hoping it would help the compiler deduce the type of the Params... pack within ParameterSet from the initialization used in the function call. However, I realized that I am mixing up 2 independent deduction contexts: the one for CTAD and the one for function parameter type deduction. In other words, the fact I have declared foo with

args::ParameterSet<Required<int, bool>, Optional<std::string>, Params...>

doesn't mean the call to foo would be able to infer that I want to have Required<int, bool> and Optional<std::string> for the first 2 template parameters of ParameterSet.

I banged my head for a few hours and at this point I am not sure what I want is possible. Would appreciate some help!

5
  • Why not use the normal language for this? void foo(bool, int); and then make overloads for the optionals - or let them take default values. Your current setup seems utterly complex for no reason.
    – Ted Lyngmo
    Commented Jul 6 at 19:19
  • One of my goals is to be able to pass the arguments to the function in any order. I could simply define void foo(auto&&... args) in that case, but I would like to embed into the signature directly, whether an argument is optional / required. The intention is to have some type checks as well to verify all required arguments are present, no non-expected arguments are present, etc. Commented Jul 6 at 19:40
  • I understand but what's the gain? You are suddenly limiting the uses to only have arguments of different types and different people will use different orders which makes it hard to read an follow.
    – Ted Lyngmo
    Commented Jul 6 at 19:43
  • suddenly limiting the uses to only have arguments of different types <-- I did explain in one of the bullet points I want to have tags, as opposed to just types. I thought that including that in the code would just overload it, but I want the eventual calling pattern to look like: foo(nameForBoolArg{true}, nameForIntArg{5}, ...), with nameForBoolArg and nameForIntArg representing named arguments. Those themselves are types, defined separately and implementing a concept that is unspecified - e.g. "NamedArgument". Commented Jul 6 at 20:08
  • It's more or less what's described It's more or less what's described fluentcpp.com/2018/12/14/named-arguments-cpp, with the extra bit of requirements of being able to specify required / optional parameters and the relevant type checks for this. Commented Jul 6 at 20:09

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.