LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   C++: template function instantiation does not work with string as return value. Why? (https://www.linuxquestions.org/questions/programming-9/c-template-function-instantiation-does-not-work-with-string-as-return-value-why-816136/)

TITiAN 06-24-2010 10:47 AM

C++: template function instantiation does not work with string as return value. Why?
 
Hi,

I'm new with templates in C++, but there is something that's really bugging me:

I can make references of a template function from one .cpp file in a .h file and use that function in another .cpp file if I instantiate the template with the appropriate types. But when the return type is std::string, linker errors occur.

EXAMPLE CODE:

main.cpp
Code:

#include "template.h"

int main (int args, char** arg)
{
        test("test");
        test(1);
        test(2);
        test(3.2);
        test(str("test"));
}

template.cpp
Code:

#include <iostream>

#include "template.h"

template <class T>
void test(T t)
{
        std::cout<<t<<std::endl;
}
/*                                            *\
  this template function is instantiated with
  the types int, double and const char*
  so the function can be called from somewhere
  else with these types:
\*                                            */
template void test <int>  (int);
template void test<double>(double);
template void test<const char*> (const char*);

template <class T>
std::string str(T t)
{
        std::string ret=std::string(t);
        return ret;
}
template std::string str <char*> (char*);

EDIT: I changed the char* instantiation to char const*, with the same result (johnsfine spotted this).

template.h
Code:

#include <string>

template <class T>
void test(T);

template <class T>
std::string str(T);

'g++ *.cpp' returns:
Code:

/tmp/ccgjbq5N.o: In function `main':
main.cpp:(.text+0x54): undefined reference to `std::basic_string<char, std::char_traits<char>, std::allocator<char> > str<char const*>(char const*)'
main.cpp:(.text+0x62): undefined reference to `void test<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)'
collect2: ld returned 1 exit status

The linker only complains about the template function that returns a string, not about the others (if you leave the function with the string entirely out of it, the whole thing links ond works).

Is there a (practicable) solution for this or do I just have to move templates that won't work like that into header files?

Sergei Steshenko 06-24-2010 03:58 PM

Well, I am no C++ expert, but I see 'main.cpp' includes 'template.h', and I do not see that 'template.h' includes 'template.cpp'. Nor 'main.cpp' includes 'template.cpp'.

So, if my observation is correct, why do you have 'template.cpp' in the first place ?

TITiAN 06-24-2010 04:27 PM

Quote:

Originally Posted by Sergei Steshenko (Post 4014000)
Well, I am no C++ expert, but I see 'main.cpp' includes 'template.h', and I do not see that 'template.h' includes 'template.cpp'. Nor 'main.cpp' includes 'template.cpp'.

So, if my observation is correct, why do you have 'template.cpp' in the first place ?

The last code block shows the output of g++ *.cpp. This means that 'template.cpp' is included in compiling the executable. For functions in 'template.cpp' to be called from 'main.cpp', 'template.h' contains references.

This is the usual way to split up code in more than one .cpp file, .h files [=headers] are interfaces between the .cpp files. Here it even works with the template funtions (which are indeed usually just dumped into header files), just the function that returns a string doesn't work that way.

As I stated in my first post, everything works as long as I don't use a template function that returns a string. I'm afraid your reply misses the point (if you have further ideas, you are still invited to post them here).

One thing I can think of is, the string type seems to be a template itself (the other types aren't), which complicates things. But then I can't find out what the problem is. Maybe that one really is too complicated for me, but I'd rather want to know than to guess.

johnsfine 06-24-2010 04:38 PM

The failure to find test<std::string>() is easy to understand.

The definition of test is in a separate compilation unit, so you needed to explicitly instantiate every instance of that which you might use. That is what your code is doing with:
Code:

template void test <int>  (int);
template void test<double>(double);
template void test<const char*> (const char*);

So it isn't that std::string is the only type failing to work, but that the above three are the only types that work.

Similarly the failure to find the instantiation of str() needed for str("test"): The compiler has deduced char const* and you simply made the error of providing char* instead.

Edit: The momentary brain lapse below was in my original response. But then I realized I had const&T where you have T, which is why the compiler can't misunderstand "test" for you but did for me:

Similarly I understand the failure to find the instantiation of str() needed for str("test"). But I don't understand why that almost worked and could be fixed easily.

In many places in my own code I have written the equivalent of
template <class T> std::string str(T);
str("test")
and been frustrated that the compiler instantiated something like str<const char[5]> when I really really wanted str<char const*>

So I assumed (without knowing where to check in the C++ standard) that template deduction on "test" produces const char[5] instead of char const*. That has made a lot of my code harder to get right.

But your error messages show a much better behavior by the compiler. It has deduced char const* and you simply made the error of providing char* instead.

tuxdev 06-24-2010 04:39 PM

As the compiler is telling you, you don't haven't instantiated what you need, and the compiler can't do it itself because it can't see the implementations of those templates. Each cpp is a processed by itself, nothing from any other cpp is considered ("Translation Unit").

Just save yourself a lot of trouble and put the implementations into the header file, or include from the header file a "template implementation" file.

Sergei Steshenko 06-24-2010 04:43 PM

Quote:

Originally Posted by TITiAN (Post 4014029)
...I'm afraid your reply misses the point ...

Probably. I am trying to find answers to the following questions:
  1. Which of the three files: main.cpp, template.cpp, template.h take part in compilation ?
  2. If all three of them, how exactly template.cpp becomes known to the compiler ?

johnsfine 06-24-2010 04:54 PM

Quote:

Originally Posted by Sergei Steshenko (Post 4014038)
  1. Which of the three files: main.cpp, template.cpp, template.h take part in compilation ?
  2. If all three of them, how exactly template.cpp becomes known to the compiler ?

main.cpp and template.cpp are each compiled, but separately (even though those separate compiles might be invoked by a single g++ command.

So the information in template.h is known to the compiler during each compile (because it is #included in each). But the information in template.cpp is not know while compiling main.cpp and is usable by main.cpp only through linking (which in g++ is able to connect to already instantiated instances of a template from other compilation units, but is not able to instantiate new instances).

TITiAN 06-24-2010 05:02 PM

Quote:

Originally Posted by Sergei Steshenko (Post 4014038)
Probably. I am trying to find answers to the following questions:
  1. Which of the three files: main.cpp, template.cpp, template.h take part in compilation ?
  2. If all three of them, how exactly template.cpp becomes known to the compiler ?

main.cpp includes template.h, which is an interface to the functions in template.cpp. The compiler knows about template.cpp because I tell it on the command-line ("g++ *.cpp" < this means all .cpp files in that directory are included in the compilation process). If you are curious about how to implement this (standard) kind of code splitting yourself, this link goes to an elaborate explanation. Splitting up your code is in fact basic C++ knowledge, so I can only recommend that link (or easier explanations on how to do it).

Quote:

Originally Posted by johnsfine (Post 4014033)
But your error messages show a much better behavior by the compiler. It has deduced char const* and you simply made the error of providing char* instead.

I changed it into const char* too, with the same result I'm afraid. Maybe something interesting will come up here :) (otherwise, I'll just put those templates into header files)

johnsfine 06-24-2010 05:06 PM

Quote:

Originally Posted by TITiAN (Post 4014050)
I tried it with const char* too, with the same result I'm afraid. Maybe something interesting will come up here :) (otherwise, I'll just put those templates into header files)

Did you read my explanation of both bugs in your code? Did you try both corrections?

I don't believe you would get the same result. Even fixing just one of the two bugs should have changed the error messages.

Repost your code with your understanding of those two corrections and with the error messages if any from recompiling.

TITiAN 06-24-2010 05:24 PM

Quote:

Originally Posted by johnsfine (Post 4014054)
Did you read my explanation of both bugs in your code? Did you try both corrections?

I don't believe you would get the same result. Even fixing just one of the two bugs should have changed the error messages.

Repost your code with your understanding of those two corrections and with the error messages if any from recompiling.

I'm sorry, I thought changing "char*" into "char const*" in the instantiation was the only thing you ment. Changing it did have the same result, even though "char const*" is the correct type.

So what was the other bug? I read your posts, but I don't see anything besides the "char const*" issue.

Sergei Steshenko 06-24-2010 05:27 PM

Quote:

Originally Posted by TITiAN (Post 4014050)
... The compiler knows about template.cpp because I tell it on the command-line ("g++ *.cpp" < this means all .cpp files in that directory are included in the compilation process).
...

AFAIK C++ files are compiled separately, i.e. declarations from one file are not known in another. That's why, I think, files with template code need to be physically included into the files which instantiate the template code.

TITiAN 06-24-2010 05:44 PM

Quote:

Originally Posted by Sergei Steshenko (Post 4014068)
AFAIK C++ files are compiled separately, i.e. declarations from one file are not known in another. That's why, I think, files with template code need to be physically included into the files which instantiate the template code.

The "instantiation" turns templates into full-fledged functions, which can be referred to in headers. They only work as those that you instantiate in the .cpp file, though.

Here is a working version (without string as return parameter), so you get the idea:

main.cpp
Code:

#include "template.h"

int main (int args, char** arg)
{
        test("test");  // const char*
        test(1);      // int
        test(2);      // int
        test(3.2);    // double
}

template.cpp
Code:

#include <iostream>

//    template function:
template <class T>
void test(T t)
{
        std::cout<<t<<std::endl;
}
//  instantiations for int, double and const char*:
template void test <int>  (int);
template void test<double>(double);
template void test<const char*> (const char*);

template.h
Code:

#include <string>

//  reference to the template function:
template <class T>
void test(T);

This is a combination of templates and code splitting, which I also want to implement with template functions that return a string, but I ran into the mentioned trouble...

Compile this with the command "g++ main.cpp template.cpp" or just "g++ *.cpp".

Any ideas appreciated

I gtg soon, will check on later responses when I'm back at my PC again ;)

bigearsbilly 06-25-2010 01:17 AM

your template should be in the header file.

template.cpp should be template.hpp.
template.cpp is not needed.

then it compiles.

TITiAN 06-25-2010 02:46 AM

Quote:

Originally Posted by bigearsbilly (Post 4014384)
your template should be in the header file.

template.cpp should be template.hpp.
template.cpp is not needed.

then it compiles.

Good morning,

I know that much. That would be common practice. I'm just wondering why the instantiation method does work with simple types and does not work with the string type (note that I also posted a fully working example).

Or rather, I would like to know if it's possible to instantiate template functions that return a string and use them from different .cpp files (or object files or translation units, you get the idea). Again, this does work in the second example I posted, but not when the return type is string.

TITiAN 06-25-2010 03:24 AM

Quote:

Originally Posted by johnsfine (Post 4014054)
Did you read my explanation of both bugs in your code? Did you try both corrections?

I don't believe you would get the same result. Even fixing just one of the two bugs should have changed the error messages.

Repost your code with your understanding of those two corrections and with the error messages if any from recompiling.

I just had another look. The "char*" issue did raise up another linker error, I just didn't look at it closely.

I also spotted another mistake: the "test" function was not instantiated with the string type. After I added that instantiation, everything worked. johnsfine already explained that in his first post, and I only understood that now. Sorry for that.

So everything was just a few mistakes on my part. Thank you for your contributions.

The following code works completely, I'm just posting it for convenience:

main.cpp
Code:

#include "template.h"

int main (int args, char** arg)
{
        test("test");      // char const*
        test(1);            // int
        test(2);            // int
        test(3.2);          // double
        test(str("test"));  // string
}

template.cpp
Code:

#include <iostream>

#include "template.h"

template <class T>
void test(T t)
{
        std::cout<<t<<std::endl;
}
template void test <int>  (int);
template void test<double>(double);
template void test<const char*> (const char*);
template void test<std::string>  (std::string);

template <class T>
std::string str(T t)
{
        std::string ret=std::string(t);
        return ret;
}
template std::string str <char const*> (char const*);

template.h
Code:

#include <string>

template <class T>
void test(T);

template <class T>
std::string str(T);


graemef 06-25-2010 03:50 AM

Just for information. You don't need to do it that way. With templates (in gcc) you can include the code in the header file and then the compiler will work out which templated functions will be required. So the following will give the same results:

template.h
Code:

#include <string>
#include <iostream>

template <class T>
void test(T t)
{
        std::cout<<t<<std::endl;
}


template <class T>
std::string str(T t)
{
        std::string ret=std::string(t);
        return ret;
}

main.cpp
Code:

#include "template.h"

int main (int args, char** arg)
{
        test("test");      // char const*
        test(1);            // int
        test(2);            // int
        test(3.2);          // double
        test(str("test"));  // string
}

Note the main is unchanged I have just included it for completeness.

johnsfine 06-25-2010 05:12 AM

Quote:

Originally Posted by graemef (Post 4014494)
Just for information. You don't need to do it that way. With templates (in gcc) you can include the code in the header file and then the compiler will work out which templated functions will be required.

I think you're missing the point. Of course you can include the function definitions in the header file and not have to deal with this problem. But you might have good reasons to want to keep just function declarations in the header file and function definitions in a .cpp file.

TITiAN clearly wanted to do that and knew the basics of how to do that and merely made a couple minor errors in the details. But then started thinking there was something more fundamental wrong with the mechanism, rather than spotting the couple details that were wrong.

TITiAN 06-25-2010 05:33 AM

One thing that confused me for most of the time was that g++ calls the string type this:
Code:

std::basic_string<char, std::char_traits<char>, std::allocator<char> >
When you think of that whole expression just as "string", it all becomes clear.

Right now I'm having issues with a template function that accepts "T const&" as an argument, but before I start another thread, I'll tear out my own hair ;)

johnsfine 06-25-2010 05:55 AM

Quote:

Originally Posted by TITiAN (Post 4014567)
Right now I'm having issues with a template function that accepts "T const&" as an argument, but before I start another thread, I'll tear out my own hair ;)

Take yet another look at my main post of this thread. Notice what I said about the compiler's unfortunate behavior when it matches quoted text to a template argument of the form T const&

bigearsbilly 06-25-2010 06:55 AM

http://www.parashift.com/c++-faq-lit...html#faq-35.12

johnsfine 06-25-2010 07:41 AM

Quote:

Originally Posted by bigearsbilly (Post 4014640)

I hate links to long documents posted alone as apparent answers in complicated threads.

Whose question do you think you're answering? What part of that question do you think you're answering? What part of that long document do you think provides that answer?

The OP in this thread was never confused about the basic questions of why it is easier to have the definition in header rather than cpp files, nor the more advanced question of what you need to do when you really want the definition in a cpp anyway. The OP was only messing up some details within that and already solved even those details.

The link you posted seems to only cover the basics of that, which the OP didn't need and which others in this thread should already understand from what was already in this thread.

The OP (and maybe me as well) are now confused about some of the strange consequences of using T const& as the parameter type in a templated function. Is there something I missed in that document you linked that helps with that question?

bigearsbilly 06-25-2010 08:49 AM

well excuse me for not meeting your criteria.

I thought it may help the OP.
It is also a useful resource for C++ problems.

I thought it was fairly relevent myself,
the FAQ says:
Quote:

Why can't I separate the definition of my templates class from its declaration and put it inside a .cpp file?
which is what the OP was attempting.
if you read the FAQ it refers to the next two which give solutions to the problem.
OP:
Quote:

But when the return type is std::string, linker errors occur.
next FAQ:
Quote:

How can I avoid linker errors with my template functions?
one after that....
Quote:

How does the C++ keyword export help with template linker errors?

obviously I am an idiot.

bigearsbilly 06-25-2010 09:01 AM

Quote:

Originally Posted by johnsfine (Post 4014674)

The OP (and maybe me as well) are now confused about some of the strange consequences of using T const& as the parameter type in a templated function. Is there something I missed in that document you linked that helps with that question?

give me an example, maybe I can get an answer to you.

johnsfine 06-25-2010 10:19 AM

Quote:

Originally Posted by bigearsbilly (Post 4014761)
give me an example, maybe I can get an answer to you.

I'm not sure I have an example where I don't know the answer. Maybe TITiAN will post one.

I have complicated examples, some of which simplify to the example below and others I'm not sure. When I started being confused by this topic 8 years ago, I would not have understood the behavior of the complicated examples that do simplify to the one below. Once it is that simplified as shown below, the behavior is much easier to understand. But I expect many experienced C++ programmers would be surprised at the output of even this simple program:

Code:

#include <iostream>

template <class T> void test1(T const& t) {
    std::cout << "size = " << sizeof(t) << std::endl;
}

template <class T> void test2(T t) {
    std::cout << "when going through test2 ";
    test1(t);
}

int main (int args, char** arg) {
    test1("text");
    test2("text");
    test1(1.3);
    test2(1.3);
    return 0;
}

Code:

size = 5
when going through test2 size = 4
size = 8
when going through test2 size = 8

Why is a double the same size in test1 as in test2, while "text" is 5 bytes in test1 and 4 in test2.

In more complicated cases, I used to make the simple assumption that "test" was a char const* which is 4 bytes (in x86) so that a reference to "text" would be a reference to a char const* which would be a reference to a type that is 4 bytes, just a 1.3 is a double, which is 8 bytes while a reference to 1.3 is a reference to a type that is 8 bytes.

In other words, I thought passing by reference gives you a reference to the type that would have been passed if the same parameter had been passed by value. But that is not generally true.

When writing a very general template that must be prepared to deal with many kinds of input, some of which really ought to be passed by const&, it may be tricky to deal with this extra difference between passing by value and passing by const& (that it is a const& to some other type than would have been passed by value).

graemef 06-25-2010 08:30 PM

You'll get the same difference without templates, it's because one is measuring the size of the literal, the other measuring the size of the pointer:
Code:

#include <iostream>

int main (int args, char** arg)
{
    char * text = "text appears here";
    std::cout << sizeof(text) << std::endl;
    std::cout << sizeof("text appears here") << std::endl;
    return 0;
}

gives the following output:
Code:

4
18


johnsfine 06-26-2010 06:23 AM

Quote:

Originally Posted by graemef (Post 4015289)
You'll get the same difference without templates, it's because one is measuring the size of the literal, the other measuring the size of the pointer:

I know one is measuring the literal and one is measuring the pointer. That wasn't the tricky part. The tricky part was why. Look at how the two templates are declared and called. It is initially confusing that they behave differently.

TITiAN 06-26-2010 09:12 AM

I still don't understand why I can't use "const char* const&" as an argument:
main.cpp
Code:

#include "template.h"

int main (int args, char** arg)
{
        test("test");
        return 0;
}

template.cpp
Code:

#include <iostream>

#include "template.h"

template <class T>
void test(T const& t)
{
        cout<<t<<endl;
}
template void test<const char*> (const char* const&);

template.h
Code:

#include <string>

using namespace std;

template <class T>
void test(T const&);

`g++ *.cpp' returns:
Code:

/tmp/ccS1eD8a.o: In function `main':
main.cpp:(.text+0x11): undefined reference to `void test<char [5]>(char const (&) [5])'
collect2: ld returned 1 exit status

As johnsfine mentioned before, g++ wants me to use this template instantiation for the purpose:
Code:

template void test<char[5]> (char const(&)[5]);
Obviously, it would be ridiculous to make instantiations for all possible sizes.

BTW this works:
Code:

#include <iostream>
using namespace std;

template <typename T>
void test (T const& o)
{
        cout << o << endl;
}

int main (int args, char** arg)
{
        test("test");
        return 0;
}


TITiAN 06-26-2010 09:22 AM

I figured something out: If I change this line from main.cpp
Code:

test("test);
into this:
Code:

test((const char*)"test");
it works.

So I guess the compiler can automatically convert char[5] into const char*, but not when it has to deduce that char[5] is const char* as in const char* const&.

TITiAN 06-26-2010 09:41 AM

Dear Diary,
Today was a lucky day: I found a solution for the problem the use of the "T const&" template type has.

The working version looks like this:
main.cpp
Code:

#include "template.h"

int main (int args, char** arg)
{
        test("test");
        return 0;
}

template.cpp
Code:

#include <iostream>

#include "template.h"

template <class T>
void test(T const& t)
{
        cout<<t<<endl;
}
void test(char const* t) {test<char const*>(t);}

template.h
Code:

#include <string>

using namespace std;

template <class T>
void test(T const&);

void test(char const*);

That's a practicable solution that even works with the const& statement, I suppose.

The function
Code:

void test(char const* t) {test<char const*>(t);}
splits up the automatic conversions (char[5]->char const* and char const*->char const* const&).

graemef 06-27-2010 02:03 AM

I wonder if there has been some confusion about how references work?

As I understand it the reference is established in the declaration and not in the calling of a function. So if you pass a reference of an object to a function that is not expecting a reference then a copy is made. Whereas if in the declaration the function says that it expects a reference then a reference will be used.

The following example first looks at it from a function perspective and then a templated function.

template.h
Code:

#include <string>
#include <iostream>

using std::cout;

class MyClass
{
private:
  int number;
public:
  MyClass (int num):number(num){cout << "In constructor.\n";}
  MyClass (const MyClass& obj):number(obj.number){cout << "In copy constructor.\n";}
  friend std::ostream& operator<<(std::ostream& os, const MyClass& obj) {os << obj.number<<std::endl; return os;}
};


template <class T>
void ttest(T const & t);
void test (MyClass obj);
void rtest (MyClass const & obj);

template.cpp
Code:

#include "template.h"

template <class T>
void ttest(T const & t)
{
        std::cout<<t<<std::endl;
}
template void ttest<MyClass>(MyClass const &);

void test(MyClass obj)
{
        std::cout<<obj<<std::endl;
}

void rtest(MyClass const & obj)
{
        std::cout<<obj<<std::endl;
}

main.cpp
Code:

#include "template.h"

int main (int args, char** arg)
{
  const MyClass mc(3);
  ttest(mc);
  rtest(mc);
  test(mc);
  MyClass const & ref = mc;
  ttest(ref);
  rtest(ref);
  test(ref);
}

output
Code:

In constructor.
3

3

In copy constructor.
3

3

3

In copy constructor.
3

The two functions test() and rtest() are just normal functions. When test() is called a copy is made. Whenever rtest() is called regardless of whether the object or reference to the object is called no copy is done. The function ttest() is just the rtest() function converted to a template function.

My recommendation would be to have the concepts clear by writing a non-templated function if the creation of the templated version is becoming problematic


All times are GMT -5. The time now is 09:06 AM.