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

Template instantiation: Why is this method instantiated?‏

70 views
Skip to first unread message

John Doe

unread,
Jun 3, 2012, 3:47:48 PM6/3/12
to
(Note: Only the introduction is linux specific.)
On Linux there are two functions called strerror_r. One can choose
between those function using appropriate preprocessor flags. The problem
is that the functions have different return types which is why I tried
to wrap the handling code in another C++ function. I wanted to hide the
distinction using templates.

The idea is to create template specializations for each implementation
and let the compiler decide which one to pick. For some reason g++ 4.5.3
always seems to instantiate both methods and I don't know why.

Here is what I think should happen:
1. The compiler evaluates Equals<GNU_type, T> in line 83
2. The compiler instantiates Call<true> (on *my* system is true).
Call<false> is not instantiated.
3. The compiler instantiates Strerror_r<GNU_type> because of 2.). Also
because of 2.), Strerror_r<Posix_type> is never instantiated.
4. The compiler sees fitting returns types in line 19, the mismatch in
line 36 is ignored.

Where is my mistake?

Compiler: g++ 4.5.3

Error Message:
map.cpp: In member function ‘int Strerror_r<int (*)(int, char*, long
unsigned int)>::posix(int, char*, size_t)’:
map.cpp:36:40: error: invalid conversion from ‘char*’ to ‘int’

(On my system the GNU's strerror_r version is used (char* return type))


Code:
#include <cstdio>
#include <cerrno>
#include <cstring> // strerror_r


typedef char* (*Gnu_strerror_r_type)(int, char*, size_t);
typedef int (*Posix_strerror_r_type)(int, char*, size_t);


template<typename Fun>
struct Strerror_r;


template<>
struct Strerror_r<Gnu_strerror_r_type>
{
int gnu(int error, char* buffer, size_t size)
{
char* p( strerror_r(error, buffer, size) ); // line 19
if( !p || p == buffer )
return 0;

strncpy(buffer, p, size-1);
buffer[size-1] = 0;
return 0;
}
};



template<>
struct Strerror_r<Posix_strerror_r_type>
{
// Why is this instantiated???
int posix(int error, char* buffer, size_t size)
{
return strerror_r(error, buffer, size); // Line 36 here
}
};



template<bool value>
struct Call;


template<>
struct Call<true>
{
Strerror_r<Gnu_strerror_r_type> s;
int execute(int e, char* p, size_t sz) { return s.gnu(e,p,sz); }
};



template<>
struct Call<false>
{
Strerror_r<Posix_strerror_r_type> s;
int execute(int e, char* p, size_t sz) { return s.posix(e,p,sz); }
};



template<typename T, typename U>
struct Equals
{
static const bool value = false;
};


template<typename T>
struct Equals<T, T>
{
static const bool value = true;
};



template<typename T>
int my_strerror_r_impl(
T, int error, char* buffer, size_t size)
{
Call<Equals<Gnu_strerror_r_type, T>::value> c; // line 83
return c.execute(error, buffer, size);
}



int my_strerror_r(int error, char* buffer, size_t size)
{
return my_strerror_r_impl(&strerror_r, error, buffer, size);
}



int main()
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
if( my_strerror_r(ENOSYS, buffer, sizeof(buffer)) < 0 )
return 1;

std::printf( "Message: '%s'\n", buffer );
}





--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Johannes Schaub

unread,
Jun 3, 2012, 7:55:09 PM6/3/12
to
Am 03.06.2012 21:47, schrieb John Doe:
> (Note: Only the introduction is linux specific.)
> On Linux there are two functions called strerror_r. One can choose
> between those function using appropriate preprocessor flags. The problem
> is that the functions have different return types which is why I tried
> to wrap the handling code in another C++ function. I wanted to hide the
> distinction using templates.
>
> The idea is to create template specializations for each implementation
> and let the compiler decide which one to pick. For some reason g++ 4.5.3
> always seems to instantiate both methods and I don't know why.
>
[...]
> Code:
> #include<cstdio>
> #include<cerrno>
> #include<cstring> // strerror_r
>
>
> typedef char* (*Gnu_strerror_r_type)(int, char*, size_t);
> typedef int (*Posix_strerror_r_type)(int, char*, size_t);
>
>
> template<typename Fun>
> struct Strerror_r;
>
>
> template<>
> struct Strerror_r<Gnu_strerror_r_type>

This one...

>
> template<>
> struct Strerror_r<Posix_strerror_r_type>

And this one define no templates, but classes. There is no instantiation
to be done further - they are already types and they are fully parsed
and type-checked (no dependent things occur anymore).

Your code contains a type-error so it was correctly diagnosed by the
compiler.

>
> template<bool value>
> struct Call;
>
>
> template<>
> struct Call<true>
> {
> Strerror_r<Gnu_strerror_r_type> s;
> int execute(int e, char* p, size_t sz) { return s.gnu(e,p,sz); }
> };
>
>
>
> template<>
> struct Call<false>
> {
> Strerror_r<Posix_strerror_r_type> s;
> int execute(int e, char* p, size_t sz) { return s.posix(e,p,sz); }
> };
>

Even if you fix the above problem and make Strerror_r structs templates,
that won't solve the issue because these Call classes too are not
templates. Their member "s" will instantiate your templates to generated
classes that are then typechecked and render the program ill-formed again.

>
>
> template<typename T, typename U>
> struct Equals
> {
> static const bool value = false;
> };
>
>
> template<typename T>
> struct Equals<T, T>
> {
> static const bool value = true;
> };
>
>
>
> template<typename T>
> int my_strerror_r_impl(
> T, int error, char* buffer, size_t size)
> {
> Call<Equals<Gnu_strerror_r_type, T>::value> c; // line 83
> return c.execute(error, buffer, size);
> }
>
>
>
> int my_strerror_r(int error, char* buffer, size_t size)
> {
> return my_strerror_r_impl(&strerror_r, error, buffer, size);
> }
>
>

All this seems like a bit too much code to solve the problem you
described, but perhaps your actual problem is more involved than it
seems to me. To solve your issue while keeping your approach, you can
make "Call" and "Strerror_r" partial specializations (which are class
templates). Don't shy away from introducing dummy parameters.

// ---------------------
template<typename Type, typename /*Ignored*/>
struct First {
typedef Type type;
};

template<bool value, typename DependentType = void>
struct Call;

template<typename DependentType>
struct Call<true, DependentType>
{
typename First<
Strerror_r<Gnu_strerror_r_type>, DependentType>::type s;
int execute(int e, char* p, size_t sz) { return s.gnu(e,p,sz); }
};

template<typename DependentType>
struct Call<false, DependentType>
{
typename First<
Strerror_r<Posix_strerror_r_type>, DependentType>::type s;
int execute(int e, char* p, size_t sz) { return s.posix(e,p,sz); }
};
// ---------------------

Note that for the member definition, you too need a dependent type for
"s" to delay instantiating "Strerror_r<Posix_strerror_r_type>" until
Call is instantiated.

You can do similar things to Strerror_r to make your code compile.

Zoltan Juhasz

unread,
Jun 3, 2012, 7:55:38 PM6/3/12
to
On Sunday, 3 June 2012 15:47:48 UTC-4, John Doe wrote:
> Here is what I think should happen:
> 1. The compiler evaluates Equals<GNU_type, T> in line 83
> 2. The compiler instantiates Call<true> (on *my* system is true).
> Call<false> is not instantiated.
> 3. The compiler instantiates Strerror_r<GNU_type> because of 2.). Also
> because of 2.), Strerror_r<Posix_type> is never instantiated.
> 4. The compiler sees fitting returns types in line 19, the mismatch in
> line 36 is ignored.
>
> Where is my mistake?


You fully (explicitly) specialize the primary template, and
explicitly specialized templates are all instantiated implicitly,
including their non-template members.

Try to remove the call-side reference (in 'my_strerror_r_impl')
and it will still fail, since both 'Call' and 'Sterror_r'
specializations are instantiated.

You'll need to use partial template specialization on those
instantiation paths, which leads to an invalid code. In this case
'Call' and 'Strerror_r' templates have to be modified, since
one of the 'Strerror_r' contains invalid code.

-- Zoltan

John Doe

unread,
Jun 4, 2012, 9:09:58 AM6/4/12
to
On 06/04/2012 01:55 AM, Johannes Schaub wrote:
> [snip]
>> template<typename Fun>
>> struct Strerror_r;
>>
>>
>> template<>
>> struct Strerror_r<Gnu_strerror_r_type>
>
> This one...
>
>>
>> template<>
>> struct Strerror_r<Posix_strerror_r_type>
>
> And this one define no templates, but classes. There is no
> instantiation to be done further - they are already types and they
> are fully parsed and type-checked (no dependent things occur
> anymore).

I see.

> [snip]
> All this seems like a bit too much code to solve the problem you
> described, but perhaps your actual problem is more involved than it
> seems to me.

It was too complicated. Here is the simplified version:

#include <cstdio>
#include <cerrno>
#include <cstring> // strerror_r


typedef char* (*Gnu_strerror_r_type)(int, char*, size_t);
typedef int (*Posix_strerror_r_type)(int, char*, size_t);


template<typename Fun, typename Dummy>
struct Strerror_r;


// Dummy prevents full specialization
// full specialization causes instantiation
template<typename Dummy>
struct Strerror_r<Gnu_strerror_r_type, Dummy>
{
static int execute(int error, char* buffer, size_t size)
{
char* p( strerror_r(error, buffer, size) );
if( !p || p == buffer )
return 0;

strncpy(buffer, p, size-1);
buffer[size-1] = 0;
return 0;
}
};



template<typename Dummy>
struct Strerror_r<Posix_strerror_r_type, Dummy>
{
int execute(int error, char* buffer, size_t size)
{
return strerror_r(error, buffer, size);
}
};



template<typename T>
int my_strerror_r_impl(
T, int error, char* buffer, size_t size)
{
typedef Strerror_r<T, int> Implementation;
return Implementation::execute(error, buffer, size);
}



int my_strerror_r(int error, char* buffer, size_t size)
{
return my_strerror_r_impl(&strerror_r, error, buffer, size);
}



int main()
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
if( my_strerror_r(ENOSYS, buffer, sizeof(buffer)) < 0 )
return 1;

std::printf( "Message: '%s'\n", buffer );
}




itaj sherman

unread,
Jun 5, 2012, 1:04:31 PM6/5/12
to
On Jun 4, 4:09 pm, John Doe <ab4...@hotmail.com> wrote:

[snip]

>
> It was too complicated. Here is the simplified version:
>
> #include <cstdio>
> #include <cerrno>
> #include <cstring> // strerror_r
>
> typedef char* (*Gnu_strerror_r_type)(int, char*, size_t);
> typedef int (*Posix_strerror_r_type)(int, char*, size_t);
>
> template<typename Fun, typename Dummy>
> struct Strerror_r;
>
> // Dummy prevents full specialization
> // full specialization causes instantiation
> template<typename Dummy>
> struct Strerror_r<Gnu_strerror_r_type, Dummy>
> {
> static int execute(int error, char* buffer, size_t size)
> {
> char* p( strerror_r(error, buffer, size) );
> if( !p || p == buffer )
> return 0;
>
> strncpy(buffer, p, size-1);
> buffer[size-1] = 0;
> return 0;
> }
>
> };
>
> template<typename Dummy>
> struct Strerror_r<Posix_strerror_r_type, Dummy>
> {
> int execute(int error, char* buffer, size_t size)
> {
> return strerror_r(error, buffer, size);
> }
>
> };

While parsing the template the compiler can determine that the call
for strerror_r cannot compile for any possible instantiation. This is
enough to cause a compilation error for the template, it doesn't need
look for an actual instantiation.
As others said, this is because "strerror_r" doesn't depend on the
template's parameters.
In most cases (maybe all) that a template cannot be instatiated, the
standard already considers it a compilation error even if it is never
actaully tried to be instatiated.
The "Dummy" parameter doesn't affect that.

However you could change the code, so that the actual call expression
uses a parameter that has the right type.
A parameter of a template as my example here below (or otherwise it
could be a parameter of the function).

struct Strerror_r<Posix_strerror_r_type, Dummy>
{
template< Posix_strerror_r_type the_function >
static //<-- you forgot static?
int execute(int error, char* buffer, size_t size)
{
return (the_function)(error, buffer, size);
}
};

struct Strerror_r<Gnu_strerror_r_type, Dummy>
{
template< Gnu_strerror_r_type the_function >
static int execute(int error, char* buffer, size_t size)
...
char* p( (the_function)(error, buffer, size) );
...

//notice that clearly "(the_function)" is dependent.
//don't really need the "Dummy" anymore.

...
return Implementation::execute< &strerror_r >(error, buffer, size);

[snip]

itaj

wasti...@gmx.net

unread,
Jun 5, 2012, 1:08:33 PM6/5/12
to
On Sunday, June 3, 2012 9:47:48 PM UTC+2, John Doe wrote:
> (Note: Only the introduction is linux specific.)

> On Linux there are two functions called strerror_r. One can choose
> between those function using appropriate preprocessor flags. The
> problem is that the functions have different return types which is
> why I tried to wrap the handling code in another C++ function. I
> wanted to hide the distinction using templates.

Why? If a preprocessor flag switches between signatures, use the
preprocessor to switch between implementations.

int my_strerror_r(int error, char* buffer, size_t size)
{
#if defined(the_flag_that_chooses_gnu)
char* p( strerror_r(error, buffer, size) );
if( !p || p == buffer )
return 0;

strncpy(buffer, p, size-1);
buffer[size-1] = 0;
return 0;
#else
return strerror_r(error, buffer, size);
#endif
}

So much simpler.

Sebastian

Johannes Schaub

unread,
Jun 5, 2012, 8:31:45 PM6/5/12
to
I brought it in so that the questioner could use it to make constructs
dependent as needed, but I didn't realize I should have hinted him to
this follow-up issue too. In this case, an alternative to making the
call itself dependent is to make the types dependent, so that it looks like

template<typename Dummy>
struct Strerror_r<Gnu_strerror_r_type, Dummy>
{
static int execute(int error, char* buffer, size_t size)
{
typename First<char*, Dummy>::type p(
strerror_r(error, buffer, size) );
/* unchanged ... */
}
};


template<typename Dummy>
struct Strerror_r<Posix_strerror_r_type, Dummy>
{
typename First<int, Dummy>::type
execute(int error, char* buffer, size_t size)
{
return strerror_r(error, buffer, size);
}
};


0 new messages