LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (http://www.linuxquestions.org/questions/programming-9/)
-   -   C++: template function instantiation does not work with string as return value. Why? (http://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);



All times are GMT -5. The time now is 03:58 AM.