Terrance Kennedy
Terrance Kennedy

Reputation: 343

Failure to pass pointer to const data as function template argument

I have a function which takes a function pointer as an argument, and then calls that function with its own arguments:

typedef int (*my_func_ptr)( int );

int foo( my_func_ptr f ) {
    static int i = 0;
    return i = f( i );
}

Sometimes, I need to pass functions to foo that depend on more than just integer input to spit out a result.

int add_strlen( int i, const char* s ) {
    return i + strlen( s );
}

I could rework the above code to make use of std::function and then use std::bind, but it is preferable to me that these functions be created at compile time, so I'm using templates.

template<const char* S>
int add_strlen( int i ) {
    return i + strlen( S );
}

/**
 * Usage:
 * char bar[] = "bar";
 * foo( add_strlen<bar> );
 */

My problem arises when using pointers as template arguments. Whenever I use a pointer to constant data of any type as a template argument, it only manages to compile if the argument being passed is declared as a non-const array of that type.

char char_array[]             = "works";
const char const_char_array[] = "error";
char *char_ptr                = "error";
const char *const_char_ptr    = "error";

The relevant error in Clang (ver. 3.0-6) (errors for char_ptr and const_char_ptr are the same):

func_ptr.cpp:29:9: error: no matching function for call to 'foo'
        foo( add_strlen<const_char_array> );
        ^~~
func_ptr.cpp:6:5: note: candidate function not viable: no overload of 'add_strlen' matching 'my_func_ptr' (aka 'int (*)(int)') for 1st argument
int foo( my_func_ptr f )

Can anyone explain to me why this is? The way I see it, template parameter S is expected to be of type const char*, which in any other circumstance means I can pass in any const or non-const pointer or array of type char and expect it to work. I would like to be able to declare my arrays as const, because I don't want to even imply that they are meant to be modified at runtime. Is there any way to keep my arrays const and use them as template arguments?

Edit: Thanks to some help (and a newer version of Clang with better errors) I was able to determine that supplying a template argument with internal linkage is part of the problem. By declaring the above variables as extern, I am able to use add_strlen<const_char_array> without error. I've also created a simplified test case. It is included below:


#include <cstring>

typedef int (*my_func_ptr)( int );

int foo( my_func_ptr f ) {
    static int i = 0;
    return i = f( i );
}

template<const char* S>
int add_strlen( int i ) {
    return i + strlen( S );
}

extern char char_array[];
extern const char const_char_array[];
extern char *char_ptr;
extern const char *const_char_ptr;

char char_array[] = "foo";
const char const_char_array[] = "bar";
// assigning to string literal is deprecated
char *char_ptr = char_array;
const char *const_char_ptr = "baz";

int main(int argc, const char *argv[])
{
    foo( add_strlen<char_array> ); // works
    foo( add_strlen<const_char_array> ); // works
    //foo( add_strlen<char_ptr> ); // doesn't work
    //foo( add_strlen<const_char_ptr> ); // doesn't work
    return 0;
}

Upvotes: 3

Views: 932

Answers (1)

X-Istence
X-Istence

Reputation: 16667

The error seems to be related to what you are and what you are not allowed to use as non-type template parameters, referring to IBM Linux Compilers documentation for Non-type template parameters they have this to say:

The syntax of a non-type template parameter is the same as a declaration of one of the following types:

  • integral or enumeration
  • pointer to object or pointer to function
  • reference to object or reference to function
  • pointer to member

The reason why char_array[] and const_char_array[] work when passed in is because they are constant at compile time and will never change underneath the program while it is running. Integral types can be passed in, pointers to integral types however can not be passed in.

The template is expecting a type of const char * a.k.a const char[x], but it is also expecting something that will never change, so the location where the pointer is pointing may never change. When passed in at compiler time your const_char_array it is being passed a char[6] ("error"). The location will never change and the contents will never change. However when passing in the const_char_ptr it is getting a const char *, while the pointer itself may never change, it is entirely possible the location where it points may change. It itself is not static.

char *_arr = new char[20];
const char* _ptr_arr = _arr;

We can agree here that my _ptr_arr is the exact same type as your const_char_ptr, yet the location where the contents are stored may change at run-time. In templates that isn't allowed since it may require a whole new instantiation of the template, and is non-deterministic from when templates are created. A char [6] is static and won't change.

foo( add_strlen<_ptr_arr> );

results in the following compiler error:

test.cpp:36:5: error: no matching function for call to 'foo'
    foo( add_strlen<_ptr_arr>);
    ^~~
test.cpp:6:5: note: candidate function not viable: no overload of 'add_strlen' matching 'my_func_ptr' (aka 'int (*)(int)') for 1st argument
int foo( my_func_ptr f ) {
    ^

Which is not very helpful, we want to figure out why there is no valid overload, compiling the code with the function stand-alone without being passed as a function pointer we get the following:

add_strlen<_ptr_arr>(0);

will result in:

test.cpp:36:5: error: no matching function for call to 'add_strlen'
    add_strlen<_ptr_arr>(0);
    ^~~~~~~~~~~~~~~~~~~~
test.cpp:16:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'S'
int add_strlen( int i ) {
    ^

So the explicitly-specified argument is invalid, specifically, we can't pass in an pointer to an integral.

Upvotes: 1

Related Questions