Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Variadic template parameters as const references

24 views
Skip to first unread message

Juha Nieminen

unread,
Apr 12, 2020, 2:38:46 AM4/12/20
to
Suppose I wanted to write a variadic template function but so that
the function takes all of its parameters as const references (rather
than universal references). No problem, right?

template<typename... Params_t>
void foo(const Params_t&... params);

The problem with this is that this effetively "removes const" from
the supplied types. Or, rather, Params_t expands to the non-const
versions of the input types. So, for example, calling it with
something like:

foo("hello");

will make Params_t be a "char*", not a "const char*".

This isn't a problem as long as "Params_t" itself isn't used inside
the function. It can become a problem if it is being used. For example
something like

std::tuple<Params_t...> values;

becomes problematic because it expands to

std::tuple<char*> values;

and you cannot assign a const char* to that.

Is there a way to take all parameters to a variadic template function
as const references *without* removing the "constness" of the types
in the parameter pack?

Ben Bacarisse

unread,
Apr 12, 2020, 6:51:57 AM4/12/20
to
Juha Nieminen <nos...@thanks.invalid> writes:

> Suppose I wanted to write a variadic template function but so that
> the function takes all of its parameters as const references (rather
> than universal references). No problem, right?
>
> template<typename... Params_t>
> void foo(const Params_t&... params);
>
> The problem with this is that this effetively "removes const" from
> the supplied types. Or, rather, Params_t expands to the non-const
> versions of the input types. So, for example, calling it with
> something like:
>
> foo("hello");
>
> will make Params_t be a "char*", not a "const char*".

Won't it make it char [6]? When taking a reference, "hello" has type
const char [6].

> This isn't a problem as long as "Params_t" itself isn't used inside
> the function. It can become a problem if it is being used. For example
> something like
>
> std::tuple<Params_t...> values;
>
> becomes problematic because it expands to
>
> std::tuple<char*> values;
>
> and you cannot assign a const char* to that.

I think it is equivalent to std::tuple<char [6]> and you can't assign
arrays so the const issue is not the main issue.

You can call foo((const char *)"hello") and then, in foo, assign

std::get<0>(values) = "goodbye";

but that's really clumsy. I don't know how you can make a string
literal convert to a pointer when you have a reference parameter pack.

> Is there a way to take all parameters to a variadic template function
> as const references *without* removing the "constness" of the types
> in the parameter pack?

I don't think the constness is being removed.

--
Ben.

Juha Nieminen

unread,
Apr 12, 2020, 11:20:25 AM4/12/20
to
Ben Bacarisse <ben.u...@bsb.me.uk> wrote:
>> The problem with this is that this effetively "removes const" from
>> the supplied types. Or, rather, Params_t expands to the non-const
>> versions of the input types. So, for example, calling it with
>> something like:
>>
>> foo("hello");
>>
>> will make Params_t be a "char*", not a "const char*".
>
> Won't it make it char [6]? When taking a reference, "hello" has type
> const char [6].

You may be right. However, the problem I'm referring to also exhibits
itself with things like:

const char* str = "hello";
foo(str);

Params_t will expand to char*, not const char*, so it becomes problematic
if I would want to use Params_t inside the function.

The "universal reference" syntax kind of fixes *that* particular problem,
but introduces others (in a way, it's "too good" for this purpose).
If I really wanted to take all parameters by const reference, not by
universal reference, it seems difficult.

Marcel Mueller

unread,
Apr 12, 2020, 1:52:52 PM4/12/20
to
Am 12.04.20 um 17:20 schrieb Juha Nieminen:
> You may be right. However, the problem I'm referring to also exhibits
> itself with things like:
>
> const char* str = "hello";
> foo(str);
>
> Params_t will expand to char*, not const char*, so it becomes problematic
> if I would want to use Params_t inside the function.

I see no problem here.

Either you want catch const T& /and/ T&. In this case remove the cont
from the function definition.

Or your function should only work with const references. In this case
write const Params_t inside your class, not only in the functions
parameter list. E.g.:
std::tuple<const Params_t...> values;


Marcel

Alf P. Steinbach

unread,
Apr 12, 2020, 4:22:30 PM4/12/20
to
On 12.04.2020 17:20, Juha Nieminen wrote:
> Ben Bacarisse <ben.u...@bsb.me.uk> wrote:
>>> The problem with this is that this effetively "removes const" from
>>> the supplied types. Or, rather, Params_t expands to the non-const
>>> versions of the input types. So, for example, calling it with
>>> something like:
>>>
>>> foo("hello");
>>>
>>> will make Params_t be a "char*", not a "const char*".
>>
>> Won't it make it char [6]? When taking a reference, "hello" has type
>> const char [6].
>
> You may be right. However, the problem I'm referring to also exhibits
> itself with things like:
>
> const char* str = "hello";
> foo(str);
>
> Params_t will expand to char*, not const char*, so it becomes problematic
> if I would want to use Params_t inside the function.

Since it's a reference it won't remove the pointee's `const`.

It does remove the item `const`-ness for an array type, because a const
raw array of T is a raw array of const T.


---------------------------------------------------------------
#include <iostream>
#include <typeinfo>
using namespace std;

template< class... Params_t >
void foo( const Params_t&... )
{
((cout << typeid( Params_t ).name() << endl), ...);
}

auto main()
-> int
{
const char* s = "Hello";
const auto& t = "Hello";
foo( s, t );
}
---------------------------------------------------------------


Result with Visual C++ 2019:


char const * __ptr64
char [6]



> The "universal reference" syntax kind of fixes *that* particular problem,
> but introduces others (in a way, it's "too good" for this purpose).
> If I really wanted to take all parameters by const reference, not by
> universal reference, it seems difficult.


In short, there is a problem, but not the one you think.


- Alf

Ben Bacarisse

unread,
Apr 12, 2020, 4:51:15 PM4/12/20
to
Juha Nieminen <nos...@thanks.invalid> writes:

> Ben Bacarisse <ben.u...@bsb.me.uk> wrote:
>>> The problem with this is that this effetively "removes const" from
>>> the supplied types. Or, rather, Params_t expands to the non-const
>>> versions of the input types. So, for example, calling it with
>>> something like:
>>>
>>> foo("hello");
>>>
>>> will make Params_t be a "char*", not a "const char*".
>>
>> Won't it make it char [6]? When taking a reference, "hello" has type
>> const char [6].
>
> You may be right. However, the problem I'm referring to also exhibits
> itself with things like:
>
> const char* str = "hello";
> foo(str);
>
> Params_t will expand to char*, not const char*, so it becomes problematic
> if I would want to use Params_t inside the function.

Hmm... Not as far as I can tell. Can you give and short example. If I
write:

#include <tuple>

template<typename... Params_t>
void foo(const Params_t&...)
{
std::tuple<Params_t...> t;
std::get<0>(t) = "text";
}

int main()
{
const char *s;
foo(s);
char *t;
foo(t);
}

the second foo causes problems but not the first.

> The "universal reference" syntax kind of fixes *that* particular problem,
> but introduces others (in a way, it's "too good" for this purpose).
> If I really wanted to take all parameters by const reference, not by
> universal reference, it seems difficult.

I am not sure I follow, sorry.

--
Ben.

Juha Nieminen

unread,
Apr 13, 2020, 1:53:36 AM4/13/20
to
Alf P. Steinbach <alf.p.stein...@gmail.com> wrote:
> Since it's a reference it won't remove the pointee's `const`.
>
> It does remove the item `const`-ness for an array type, because a const
> raw array of T is a raw array of const T.

Everybody's right. I was confused because I didn't really understand
the significance of a string literal parameter being actually of an
array type rather than a pointer type.

When given a string literal like "hello", Params_t expands to
char[6] (with its "constness removed"), but the variable itself
will be of a const char[6] type. This can be used in a quite
verbose and roundaway way to get the actual type one wants, like
this, for instance:

template<typename... Params_t>
void foo(const Params_t&... params)
{
std::tuple<std::decay_t<std::remove_reference_t<decltype(params)>>...>
values(params...);
}

(It also works without std::remove_reference_t, but in that case I think
the tuple will end up with references to the parameters rather than
they values. I suppose it depends on the use case which one is the
better way.)

One would think there should be an easier way of doing this.
In other words, one would think it would be possible to use Params_t
directly, rather than the "hack" of using decltype(params).
0 new messages