Failure to pass pointer to const data as function template argument - c++

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;
}

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.

Related

How to pass a char as an argument of a function inside a class using template

I'm trying to pass a char array into a function(push) inside this class(BaseStack):
template < class T >
class BaseStack
{
void push(const T&item){
//Ignore
}
}
What I tried was:
char finalstring[L];
for(int i = 0 ; i < 5; i++)
finalstring[i] = 'x';
BaseStack<char> c;
c.push(finalstring);
But my compiler said that I was doing an invalid conversion
BaseStack<char> c;
This means that your template parameter is char. This is what you specified for the template parameter. Therefore, the instance of your template is, in so many words, and very loosely speaking:
class BaseStack
{
void push(const char &item){
//Ignore
}
}
This is what happens, since your template parameter is a char. T is char, and since the parameter in the template is void push(const T &item), then that's what you get.
c.push(finalstring);
finalstring is a char array, which decays to a char *. This attempts to pass a char * to a class method that takes a (const refence to a) char. This is not allowed in C++, and that's the reason for your compilation error.
It is unclear what your intent is here, whether the template parameter should be either a const T *, or you need to pass finalstring's first character, only. Doing either will solve this particular compilation error.

Calling member function of a template class recursively

I have a working implementation of a avltree as a template class. I am adding two functions to this working implementation. These two functions that will transverse through the entire tree recursively and preform some calculations.
//avltree.cpp
//see comment in code below
template <class Comparable>
void AvlTree<Comparable>::transverseTree( AvlNode<Comparable> *t, const char *word, char matchingWords[100][MAX_LENGTH + 1], int *count) const
{
int distance;
if( t != NULL )
{
distance = levDistance(t->element/*avl word*/, word);
if (distance == 1)
{
*count++;
strcpy(matchingWords[*count], t->element/*avl word*/);
}
//error is here
transverseTree( t->left, word, matchingWords );
transverseTree( t->right, word, matchingWords );
}
}
//avltree.h
//new function
void transverseTree(AvlNode<Comparable> *t, const char *word, char matchingWords[100][MAX_LENGTH + 1],
int *count) const;
//new function
int levDistance(const char *str1, const char *str2) const;
When I try calling this function recursively, I receive this error message:
AvlTree.cpp:412:31: error: no matching function for call to ‘AvlTree<const char*>::transverseTree(AvlNode<const char*>*&, const char*&, char (*&)[34]) const’
transverseTree( t->left, word, matchingWords );
^
Why are their ampersands on the argument types to the recursive call? Are these references, and if so - how am I doing this?
You forgot to pass count in the recursive calls.
transverseTree( t->left, word, matchingWords, count ); // Missing count
transverseTree( t->right, word, matchingWords, count ); // Missing count
The signature looks like
void
AvlTree<Comparable>::transverseTree(AvlNode<Comparable> *t,
const char *word,
char matchingWords[100][MAX_LENGTH + 1],
int *count)
But your call looks like
transverseTree( t->right, word, matchingWords );
I think you forgot to pass the count pointer.
It probably has to do with your recursive calls not having the correct parameters.
void transverseTree(AvlNode<Comparable> *t, const char *word, char matchingWords[100][MAX_LENGTH + 1], int *count) const;
Here, when you declare this function, it takes in 4 parameters.
However, when you call this function recursively:
transverseTree( t->left, word, matchingWords );
You're forgetting about that last parameter *count, therefore that function you're trying to call is not defined with that particular function signature.
Ampersands don't matter here; they just allow passing an argument as a reference. Still, functions having non-reference arguments of the same type will also match (effectively requiring object copying before the function call) provided that there is a copy constructor defined (either explicitly or by default) for an argument type. In this case, the object type is a pointer, and a copy constructor is implicitly defined for it (merely copying the value). So there is no problem with that.
Still it seems the last argument, count , is missing in the recursive calls. It may be the cause of a compilation error (of course unless your have specified a default value for it in a declaration inside AvlTree class).

Calling a function with different types

I am working on an assignment in C++ and am having difficulties with a question.
We are given a function that can take anything in as an argument.We need to call this function, UberFunction, as an int, int*,int**, and char[].
My Code:
void CallUberFunctions() {
// There is a magic function, called "UberFunction" that is capable of taking
// anything for an argument. You need to call "UberFunction" with the
// following types: int, int* int** and char[]
// You also need to call "OtherUberFunction" in the namespace
// "uber_namespace" once, with no arguments.
UberFunction(42);
UberFunction((int*)42);
UberFunction((int**)42);
UberFunction((char[])'*');
}
Here is the error:
home/user/Desktop/cpp_refresher/cpp_refresher.cc:22:20: error: expected '(' for function-style
cast or type construction
I cannot get the char[] type to work properly, is my syntax above incorrect?
Try this fragment:
const int value = 42;
UberFunction(value); // int
UberFunction(&value); // int *
int * p_value = &value;
UberFunction(&p_value); // int * *
static const char text[] = "Hello World!\n";
UberFunction(text); // char []
Note that in the above example, the parameters are changing and not the return value.

Template won’t infer size of zero-length array in C++

Let’s say I have a template function that infers the length of an array parameter.
template <size_t S>
void join(const char d[], const char *(&arr)[S]) { }
If I call it like this, all is well:
const char *messages[] = {
"OK",
"Not OK",
"File not found"
};
join("\n", messages);
But if I call it with an empty array, like this:
const char *messages[] = { };
join("\n", messages);
…it doesn’t compile (with clang 4.0):
targs.cpp:9:5: error: no matching function for call to 'join'
join("\n", messages);
^~~~
targs.cpp:4:6: note: candidate template ignored: substitution failure [with S = 0]
void join(const char d[], const char *(&arr)[S]) { }
^
1 error generated.
I’m guessing that it has something to do with C++ not liking zero-length arrays, but if the function is not a template and takes the length as a separate parameter, it doesn’t complain about me declaring messages as a zero-length array.
What’s up here, and is there a nice workaround?
My actual use case is defining the parameters an HTTP API endpoint takes and looks something like this:
const api_param_t params[] = {
{ API_TYPE_STRING, "foo" },
{ API_TYPE_UINT64, "bar" },
{ API_TYPE_STRING, "baz" }
}
const api_status_t status_codes[] = { … };
const api_middleware_t middleware[] = { … };
new api_endpoint("/foo", params, status_codes, middleware);
Most endpoints take at least one parameter but many take none. It looks like this is, indeed, an extension which both GCC and clang implement (but, looks like, not completely…). I can think of a few workarounds:
Overload the api_endpoint constructor to special case zero-length arguments (but I need 23 of them to cover each zero-length-able parameter), which the GCC/clang extension is OK with.
Don’t try to infer the array length, take it as a separate parameter (and continue to use zero-length arrays)
Use a higher-level data structure like a vector for these parameters
Use a magic value to indicate "empty"
…but if anyone has better ideas I’d love to hear ‘em
This code isn't legal in the first place:
const char *messages[] = { };
Here are the errors and warnings my compiler produces:
main.cpp:6:26: warning: zero size arrays are an extension [-Wzero-length-array]
const char *messages[] = { };
^
main.cpp:7:1: error: no matching function for call to 'join'
join("\n", messages);
^~~~
main:3:6: note: candidate template ignored: substitution failure [with S = 0]: zero-length arrays are not permitted in C++
void join(const char d[], const char *(&arr)[S]) { }
^ ~
1 warning and 1 error generated.
So zero length arrays aren't actually allowed at all. Your compiler appears to have an extension for zero length arrays which, however, does not cover this specific case. Extensions are like that sometimes, because less work goes into extensions to make them work consistently with the whole language.
A workaround will depend on why you want a zero length array and how you're using it elsewhere. One workaround might be using a single element array instead.
Here's a work around. Since the extension does not allow array sizes to be deduced as zero add an overload that does not require this deduction:
template <size_t S>
void join(const char d[], const char *(&arr)[S]) {
std::cout << "array length > 0\n";
}
void join(const char d[], const char *(&arr)[0]) {
std::cout << "extension, zero length array\n";
}
int main() {
const char *messages[] = {
"OK",
"Not OK",
"File not found"
};
join("\n", messages);
const char *messages2[] = { };
join("\n", messages2);
}
You should keep in mind that this is using an extension and is not portable code. You may prefer to write portable code in order to avoid being locked into any particular C++ implementation. You can see how much you rely on this extension by adding the flag -Wzero-length-array to your builds.

non-const pointer argument to a const double pointer parameter

The const modifier in C++ before star means that using this pointer the value pointed at cannot be changed, while the pointer itself can be made to point something else. In the below
void justloadme(const int **ptr)
{
*ptr = new int[5];
}
int main()
{
int *ptr = NULL;
justloadme(&ptr);
}
justloadme function should not be allowed to edit the integer values (if any) pointed by the passed param, while it can edit the int* value (since the const is not after the first star), but still why do I get a compiler error in both GCC and VC++?
GCC: error: invalid conversion from int** to const int**
VC++: error C2664: 'justloadme' : cannot convert parameter 1 from 'int **' to 'const int **'. Conversion loses qualifiers
Why does it say that the conversion loses qualifiers? Isn't it gaining the const qualifier? Moreover, isn't it similar to strlen(const char*) where we pass a non-const char*
As most times, the compiler is right and intuition wrong. The problem is that if that particular assignment was allowed you could break const-correctness in your program:
const int constant = 10;
int *modifier = 0;
const int ** const_breaker = &modifier; // [*] this is equivalent to your code
*const_breaker = & constant; // no problem, const_breaker points to
// pointer to a constant integer, but...
// we are actually doing: modifer = &constant!!!
*modifier = 5; // ouch!! we are modifying a constant!!!
The line marked with [*] is the culprit for that violation, and is disallowed for that particular reason. The language allows adding const to the last level but not the first:
int * const * correct = &modifier; // ok, this does not break correctness of the code