Search code examples
c++templatesc++20variadic-templatesc++-concepts

Multiple template-dependent parameters


I'm not sure if the question name really reflects what I'm asking here, so please let me know if there are better titles.

I have a class that represents a value, which can be initialized either through a callable, or more cleanly through a reference to the variable.

template <typename F> concept not_callable = !std::invocable<F>;

template <typename... Args>
struct Values{
  std::tuple<std::function<Args()>...> values;

  Values(std::invocable auto&&... func): values{std::make_tuple(std::function(func)...)} {}
  Values(not_callable auto&... args): values{std::make_tuple(std::function([&args](){return args;})...)} {}
};

Values(std::invocable auto&&... func) -> Values<std::remove_cvref_t<decltype(func())>...>;
Values(not_callable auto&... arg) -> Values<std::remove_cvref_t<decltype(arg)>...>;

This allows me to construct the function through either a series of functions, or references. However, I want to be able to initialize it with a mix of these.

int x;
char* y;
Values v1 (rand, clock);
Values v2 (x, y);
// Values v3 (x, rand); Doesn't work

Note that right now it fails because I have conflicting concepts for the constructors that won't allow both to be initialized together. However without the concepts, they don't compile at all. Is there a way to make this work?

An alternate solution is to have this class contain a tuple of another class, where that class is constructible from a single var or callable.

Unfortunately that would require syntax like the following with explicit casting for each parameter.

class singleV;
Value v4(singleV(x), singleV(rand)); 

Is there a way to be able to initialize it like v3?


Solution

  • You can define a helper function to construct the corresponding std::function object according to the type of the argument

    template <typename T> 
    concept callable_or_variable = 
      std::invocable<T> || std::is_lvalue_reference_v<T>;
    
    template <callable_or_variable T>
    auto to_function(T&& t) {
      if constexpr (std::invocable<T>)
        return std::function<std::remove_cvref_t<std::invoke_result_t<T>>()>(t);
      else
        return std::function<std::remove_cvref_t<T>()>([&t] { return t; });
    }
    

    Then implement CTAD according to the return type of the std::function type returned by this helper function

    template <typename... Args>
    struct Values{
      std::tuple<std::function<Args()>...> values;
    
      Values(callable_or_variable auto&&... args): 
        values{to_function(std::forward<decltype(args)>(args))...} {}
    };
    
    template <callable_or_variable... Args>
    Values(Args&&...) -> 
        Values<std::invoke_result_t<decltype(to_function(std::declval<Args>()))>...>;
    

    Demo