Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the rules for parameter pack deduction

Tags:

c++

Can anyone tell me why this doesn't work?

template<char... cs> struct StaticString {};

template<char... tail, char... prefix>
constexpr bool startsWith(StaticString<prefix..., tail...>, StaticString<prefix...>)
{
    return true;
}

static_assert(startsWith(StaticString<'a', 'b'>(), StaticString<'a'>()),
              "ab starts with a");

Why is tail deduced to be empty?

like image 781
Ab Wilson Avatar asked Mar 07 '23 23:03

Ab Wilson


1 Answers

From cpprefrence - Parameter pack

Explanation
...
In a primary class template, the template parameter pack must be the final parameter in the template parameter list.
In a function template, the template parameter pack may appear earlier in the list provided that all following parameters can be deduced from the function arguments, or have default arguments

and cppreference - Template argument deduction

Deduction from a type
...
If P has one of the forms that include a template parameter list <T> or <I>, then each element Pi of that template argument list is matched against the corresponding template argument Ai of its A. If the last Pi is a pack expansion, then its pattern is compared against each remaining argument in the template argument list of A.

A trailing parameter pack that is not otherwise deduced, is deduced to an empty parameter pack.


To make this work, the compiler must be able to deduce the parameters. This may be done through overloads of startsWith with varying template parameters. You can start with the final part, where only the first StaticString has any remaining arguments left

template<char... tail>
constexpr bool startsWith(StaticString<tail...>, StaticString<>)
{
    return true;
}

You then have a failing startsWith, where both StaticStrings differ

template<char... tail1, char... tail2>
constexpr bool startsWith(StaticString<tail1...>, StaticString<tail2...>)
{
    return false;
}

and finally an overload where the prefix is stripped off and the remaining parts compared

template<char prefix, char... tail1, char... tail2>
constexpr bool startsWith(StaticString<prefix, tail1...>, StaticString<prefix, tail2...>)
{
    return startsWith(StaticString<tail1...>(), StaticString<tail2...>());
}

Now you can static_assert with various arguments, e.g.

static_assert(startsWith(StaticString<'a', 'b'>(), StaticString<'a'>()),
              "ab starts with a");
static_assert(startsWith(StaticString<'a', 'b'>(), StaticString<'a', 'b'>()),
              "ab starts with ab");
static_assert(startsWith(StaticString<'a', 'b'>(), StaticString<'a', 'c'>()),
              "ab does not start with ac");
static_assert(startsWith(StaticString<'a', 'b'>(), StaticString<'x', 'a'>()),
              "ab does not start with xa");

will result in (Ubuntu 16.04, g++ 5.4)

a.cpp:23:1: error: static assertion failed: ab does not start with ac
static_assert(startsWith(StaticString<'a', 'b'>(), StaticString<'a', 'c'>()), "ab does not start with ac"
^
a.cpp:24:1: error: static assertion failed: ab does not start with xa
static_assert(startsWith(StaticString<'a', 'b'>(), StaticString<'x', 'a'>()), "ab does not start with xa"
^

like image 128
Olaf Dietsche Avatar answered Mar 20 '23 07:03

Olaf Dietsche