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

implicit conversions and parameter packs

22 views
Skip to first unread message

Marcel Mueller

unread,
Aug 5, 2017, 4:32:32 AM8/5/17
to
I have the problem that implicit conversion, e.g. for CV qualifiers do
not always work with parameter packs.

The following code demonstrates the problem:


#include <cstring>
#include <cstdio>
#include <cerrno>
using namespace std;

template <typename ...ARGS>
struct MessageTemplate
{ const char* Text;
constexpr MessageTemplate(const char* text) : Text(text) {}
void print(ARGS... a) const
{ printf(Text, a...);
}
};

template <typename ...ARGS>
void printMessage(const MessageTemplate<ARGS...> msg, ARGS... a)
{ printf(msg.Text, a...);
}

static const MessageTemplate<const char*, const char*>
OPENFAILED("Failed to open file '%s': %s");

const char* filename = "Does not exist";

int main()
{
FILE* f = fopen(filename, "r");
if (f == NULL)
{ // works
OPENFAILED.print(filename, strerror(errno));
// does not work
printMessage(OPENFAILED, filename, strerror(errno));
return 1;
}
// ...
return 0;
}


The strerror function returns char* rather than const char* (for
compatibility reasons). When passed to the member function print() the
implicit conversion to const char* takes place while when calling the
free function printMessage() it does not.

I get a compiler error (gcc 4.8.4)

test3.cpp: In function ´int main()´:
test3.cpp:29:55: error: no matching function for call to
´printMessage(const MessageTemplate<const char*, const char*>&, const
char*&, char*)´
printMessage(OPENFAILED, filename, strerror(errno));
^
test3.cpp:29:55: note: candidate is:
test3.cpp:16:6: note: template<class ... ARGS> void
printMessage(MessageTemplate<ARGS ...>, ARGS ...)
void printMessage(const MessageTemplate<ARGS...> msg, ARGS... a)
^
test3.cpp:16:6: note: template argument deduction/substitution failed:
test3.cpp:29:55: note: inconsistent parameter pack deduction with
´const char*´ and ´char*´
printMessage(OPENFAILED, filename, strerror(errno));
^

The same applies to other implicit conversions like int -> long.

Why does it not work in this case?
And is there a work around other than casting every argument to the
exact type on any invocation?


Marcel

Barry Schwarz

unread,
Aug 5, 2017, 11:20:00 AM8/5/17
to
In the first case, the compiler knows that OPENFAILED is an instance
of the template with two const pointers. The compiler does not have
to create a function named print. That function was already created
when OPENFAILED was defined. Since there is an implicit conversion
from char* to const char*, the compiler has no problem performing the
conversion. If, as an example, you change the argument from
strerror(errno) to errno, you will not get the no matching function
error you receive in the second case. Instead you will get an
incompatible argument error since OPENFAILED.print cannot accept an
integer as the second argument.

In the second case, the same typename (ARGS) is used for both template
parameters in the declaration of printMessage. The presence of
OPENFAILED as the fist argument of the function call forces the ARGS
to be <const char*, const char*> for both parameters. The compiler
cannot use this template to generate an instance of the function since
the arguments corresponding to parameter a do not both have type const
char*. Changing the third argument as described above would produce
the same message. It has nothing to do with the existence, or not, of
an implicit conversion. If you change the template from ARGS and ARGS
to ARGS1 and ARGS2 and use ARGS2 for parameter a, the compiler error
disappears.

--
Remove del for email

Marcel Mueller

unread,
Aug 5, 2017, 12:43:08 PM8/5/17
to
On 05.08.17 17.19, Barry Schwarz wrote:
> In the second case, the same typename (ARGS) is used for both template
> parameters in the declaration of printMessage. The presence of
> OPENFAILED as the fist argument of the function call forces the ARGS
> to be <const char*, const char*> for both parameters. The compiler
> cannot use this template to generate an instance of the function since
> the arguments corresponding to parameter a do not both have type const
> char*. Changing the third argument as described above would produce
> the same message. It has nothing to do with the existence, or not, of
> an implicit conversion. If you change the template from ARGS and ARGS
> to ARGS1 and ARGS2 and use ARGS2 for parameter a, the compiler error
> disappears.

Hmm, doesn't this disable the type check?

Just tested, it does. With two distinct parameter packs you can pass
whatever you want, e.g. errno. This will crash the program. So it is not
an option.

OK I could work around this by forwarding the call to
MessageTemplate::print, but this is not an option either as it requires
a member function for every future function that deals with the template
and the matching parameters. (This is the idea behind the scenes, to
have different implementations.)


Marcel

Alf P. Steinbach

unread,
Aug 5, 2017, 1:52:53 PM8/5/17
to
On 05.08.2017 10:32, Marcel Mueller wrote:
> I have the problem that implicit conversion, e.g. for CV qualifiers do
> not always work with parameter packs.

It's not about parameter packs, it's just about template argument deduction.

The only implicit conversions that are applied are Derived -> Base,
because that's logically an IS-A relationship.
Here's a much simpler example:


template< class Type >
struct Type_carrier {};

template< class Arg >
void foo( Type_carrier<Arg>, Arg ) {}

auto main() -> int
{
foo( Type_carrier<bool const>{}, bool{} ); //! Nyet, ambiguous!
}


You might say, WTF!?, doesn't it /understand/ that a `bool` can be used
where a `bool const` is expected, huh?

It doesn't.



> [snip]
> And is there a work around other than casting every argument to the
> exact type on any invocation?

The usual workaround for this kind of problem is to just let the
interface's argument type be unconstrained, and convert explicitly in
the implementation the way that you want it, like this:


template< class Type >
struct Type_carrier {};

template< class Arg >
void foo_impl( Type_carrier<Arg>, Arg ) {}

template< class Arg1, class Arg2 >
void foo( Type_carrier<Arg1> t, Arg2 a )
{ foo_impl<Arg1>( t, a ); }

auto main() -> int
{
foo( Type_carrier<bool const>{}, bool{} );
}


Cheers!,

- ALf

Barry Schwarz

unread,
Aug 6, 2017, 5:30:51 AM8/6/17
to
On Sat, 05 Aug 2017 18:42:46 +0200, Marcel Mueller
<news.5...@spamgourmet.org> wrote:

>On 05.08.17 17.19, Barry Schwarz wrote:
>> In the second case, the same typename (ARGS) is used for both template
>> parameters in the declaration of printMessage. The presence of
>> OPENFAILED as the fist argument of the function call forces the ARGS
>> to be <const char*, const char*> for both parameters. The compiler
>> cannot use this template to generate an instance of the function since
>> the arguments corresponding to parameter a do not both have type const
>> char*. Changing the third argument as described above would produce
>> the same message. It has nothing to do with the existence, or not, of
>> an implicit conversion. If you change the template from ARGS and ARGS
>> to ARGS1 and ARGS2 and use ARGS2 for parameter a, the compiler error
>> disappears.
>
>Hmm, doesn't this disable the type check?
>
>Just tested, it does. With two distinct parameter packs you can pass
>whatever you want, e.g. errno. This will crash the program. So it is not
>an option.

It crashes the program because printMessage is calling printf with a
format string that specified %s and %s. However, the variadic
arguments passed to printf have type char* and int. Passing an int
when printf expects a char* causes undefined behavior. Again, this
has nothing to do with implicit conversions.

Some compilers match printf arguments against the conversion
specifications in the format string. If yours did so, it would have
reported that int is incompatible with the second %s.

>OK I could work around this by forwarding the call to
>MessageTemplate::print, but this is not an option either as it requires
>a member function for every future function that deals with the template
>and the matching parameters. (This is the idea behind the scenes, to
>have different implementations.)

Yes but one of the programmer's responsibilities is to insure that the
code generated when an instance of the template function is created is
valid for the arguments actually passed. It is possible to call many
functions with arguments that are invalid but cannot be detected by
the compiler.
0 new messages