jacobsa
jacobsa

Reputation: 6638

In C++ why can't I deduce a parameter pack for the initial arguments of a function type?

Let's say I have I have some container template type Container that accepts object types, and some std::function-like template FunctionWrapper that accepts function types. Further, I have a struct S that should be convertible to a FunctionWrapper when its signature accepts a Container<T> for some T followed by any other arguments:

#include <type_traits>

template <typename T>
struct Container {};

template <typename F>
struct FunctionWrapper {};

struct S {
  template <typename R, typename T, typename... Args>
  operator FunctionWrapper<R(Container<T>, Args...)>();
};

static_assert(std::is_convertible_v<S, FunctionWrapper<void(Container<int>)>>);
static_assert(std::is_convertible_v<S, FunctionWrapper<void(Container<int>, int)>>);
static_assert(std::is_convertible_v<S, FunctionWrapper<void(Container<int>, int, int)>>);

Clang is happy with this. However, in the codebase I'm actually working on there is a convention that the special container type always comes last. So it needs to deduce after the other arguments:

#include <type_traits>

template <typename T>
struct Container {};

template <typename F>
struct FunctionWrapper {};

struct S {
  template <typename R, typename T, typename... Args>
  operator FunctionWrapper<R(Args..., Container<T>)>();
};

static_assert(std::is_convertible_v<S, FunctionWrapper<void(Container<int>)>>);
static_assert(std::is_convertible_v<S, FunctionWrapper<void(int, Container<int>)>>);

Clang rejects this (as does gcc), saying S cannot be converted to FunctionWrapper<void(int, Container<int>)>. It seems to be a problem of template type deduction, because if I remove the Args parameter pack and spell out specific numbers of arguments clang is happy.

Which part of the standard says this shouldn't work, and why? What is the correct workaround to allow writing a conversion operator that accepts any function signature that ends in a Container<T> for some T, while also deducing T?

Upvotes: 1

Views: 48

Answers (2)

M B
M B

Reputation: 1

you can separate container into different template parameter instead of doing deductions

template <typename R, typename... Args, typename T>
operator FunctionWrapper<R(Args..., Container<T>)>();

This will probably work correctly because args... is determined before container

Here I am giving code

#include <type_traits>

template <typename T>
struct Container {};

template <typename F>
struct FunctionWrapper {};
    
struct S {
  template <typename R, typename... Args, typename T>
  operator FunctionWrapper<R(Args..., Container<T>)>();
 };

static_assert(std::is_convertible_v<S, FunctionWrapper<void(Container<int>)>>);
static_assert(std::is_convertible_v<S, FunctionWrapper<void(int, Container<int>)>>);

Upvotes: -2

jacobsa
jacobsa

Reputation: 6638

The standardese around template argument deduction is quite complicated, but I think this is related to at least one of the following two clauses:

  • [temp.deduct.type]/5:

    The non-deduced contexts are:

    • […]
    • A function parameter pack that does not occur at the end of the parameter-declaration-list.
  • [temp.deduct.type]/9:

    […] If the template argument list of P contains a pack expansion that is not the last template argument, the entire template argument list is a non-deduced context. […]

In any case parameter packs that don't come at the end are problematic. We can work around this by defining an "is instantiation" template and one for working with parameter packs, adding a type alias to the container type, and using a parameter back for the full set of function arguments:

#include <type_traits>

///////////////////////////////////////
// is_instantiation_v
///////////////////////////////////////

// An integral constant that is true if and only if T is C<Types...> for some
// set of types, i.e. T is an instantiation of the template C.
//
// This is false by default. We specialize it to true below for pairs of
// arguments that satisfy the condition.
template <template <typename...> class C, typename T>
struct is_instantiation : public std::integral_constant<bool, false> {};

template <template <typename...> class C, typename... Ts>
struct is_instantiation<C, C<Ts...>>
    : public std::integral_constant<bool, true> {};

// True if and only if the type T is an instantiation of the template C with
// some set of type arguments.
//
// Note that there is no allowance for reference or const/volatile qualifiers;
// if these are a concern you probably want to feed through std::decay_t<T>.
template <template <typename...> class C, typename T>
constexpr bool is_instantiation_v = is_instantiation<C, T>::value;

///////////////////////////////////////
// last_element_t
///////////////////////////////////////

// A struct with a type alias named `type` or the last element of the supplied
// parameter pack, which must be non-empty.
template <typename... T>
struct last_element;

template <typename... T>
using last_element_t = typename last_element<T...>::type;

// Base case: if there is only one element in the pack, that's our result.
template <typename T>
struct last_element<T> {
  using type = T;
};

// Inductive case: if there is more than one element in the pack, then the
// result is the same as the result with the first element removed.
template <typename First, typename... Others>
struct last_element<First, Others...> {
  using type = last_element_t<Others...>;
};

///////////////////////////////////////
// The interesting part
///////////////////////////////////////

template <typename T>
struct Container {
  using value_type = T;
};

template <typename F>
struct FunctionWrapper {};

struct S {
  // Can use value_type inside here for the container.
  template <typename R, typename... Args>
    requires is_instantiation_v<Container, last_element_t<Args...>>
  operator FunctionWrapper<R(Args...)>();
};

static_assert(std::is_convertible_v<S, FunctionWrapper<void(Container<int>)>>);
static_assert(std::is_convertible_v<S, FunctionWrapper<void(int, Container<int>)>>);

Clang is happy with this.

Upvotes: 2

Related Questions