1

I'm learning C++ and I'm sort of baffled by how its templates work. Let me give you an example, this works:

#include <iostream>
#include <cstdlib>
#include <type_traits>

template <class T> const T first(const T& first, const T& second) {
    return first.size()<second.size() ? first : second;
}

int main(int argc, char** argv) {
    std::string x = "the short string";
    std::string y = "the longer string";
    std::cout << "first of x, y is "<<first(y,x)<<std::endl;
}

the above code works, but what is to stop me from using an integer as my template parameter?

int main(int argc, char** argv) {
    int x = 3;
    int y = 8;
    std::cout << "first of x, y is "<<first(y,x)<<std::endl;
}

making the change above gives you a compilation error in the "first" method, not the "main" method. I read here that I can use the static_assert statement to handle this problem, but I can't figure out how to check that the type T is a basic_string or subclass at compilation time.

I wanted to know if C++ will report the problem in the right place when I use static_assert, so I switched the problem around like so:

template <class T> const T first_numeric(const T& first, const T& second) {
    static_assert(std::is_arithmetic<T>::value,"You must use a number!");
    return first<second ? first : second;
}

int main(int argc, char** argv) {
//    int x = 3;
//    int y = 8;
//    std::cout << "first_numeric of x, y is "<<first_numeric(y,x)<<std::endl;
    std::string x = "3";
    std::string y = "8";
    std::cout << "first_numeric of x, y is "<<first_numeric(y,x)<<std::endl;
}

If I had started this question with the above example, I inevitably would have been told "why on earth would you want to restrict the type parameter?" Hopefully you understand now.

In the example above, the compiler does fail like we want it to, but it fails on the wrong line. It shows the compilation error on the static_assert line, not the line where I call the method with illegal arguments.

You're probably thinking, "so what?" Well it's obvious in this example where the problem is, but it might not be when you have 10000 lines of code. Sure you can use the IDE to get a call hierarchy on that function, but if there are hundreds of places where that method is getting called, you would be checking each and every one of them just to find your mistake.

So my questions are:

  1. Is there any way to have C++ report the compilation error on the line where the method was called with illegal type parameters?
  2. If not, what guidance can you give me to avoid this problem or work around it?

===============================

Update: this is the error output

mkdir -p build/Debug/GNU-Linux-x86
rm -f "build/Debug/GNU-Linux-x86/main.o.d"
g++    -c -g -std=c++11 -MMD -MP -MF "build/Debug/GNU-Linux-x86/main.o.d" -o build/Debug/GNU-Linux-x86/main.o main.cpp
main.cpp: In instantiation of ‘const T first_numeric(const T&, const T&) [with T = std::basic_string<char>]’:
main.cpp:22:64:   required from here
main.cpp:6:5: error: static assertion failed: You must use a number!
     static_assert(std::is_arithmetic<T>::value,"You must use a number!");
     ^
make[2]: *** [build/Debug/GNU-Linux-x86/main.o] Error 1
make[2]: Leaving directory `/home/mike/NetBeansProjects/CppApplication_1'
make[1]: *** [.build-conf] Error 2
make[1]: Leaving directory `/home/mike/NetBeansProjects/CppApplication_1'
make: *** [.build-impl] Error 2

BUILD FAILED (exit value 2, total time: 460ms)

apparently the output did mention line 22 where the real problem was, but it did not highlight it in the source code. I should have read the output instead of just looking at what was highlighted.

Community
  • 1
  • 1
msknapp
  • 1,595
  • 7
  • 22
  • 39
  • *I can use the static_assert statement to handle this problem, but I can't figure out how to check that the type T is a basic_string or subclass at compilation time.* - `static_assert` *is* compile-time. – chris Sep 11 '14 at 04:10
  • 2
    Maybe you're looking for a something like [std::enable_if](http://en.cppreference.com/w/cpp/types/enable_if)? – Null Sep 11 '14 at 04:11
  • did you check this link? http://stackoverflow.com/questions/7647958/how-to-check-if-the-template-parameter-of-the-function-has-a-certain-type – Byungjoon Lee Sep 11 '14 at 04:12
  • "will report the problem in the right place" - more a matter of you getting your head around this - most compilers don't report a single location for an error - they say there's an error instantiating the template as done from some other location in a function called from somewhere else etc.. It's desirable to be told all those locations. "how to check that the type T is a basic_string or subclass at compilation time." - if that's what you want accept a `basic_string` instance by reference instead of using parametric polymorphism in a way that allows callers to pass `int`s or other types.... – Tony Delroy Sep 11 '14 at 04:12
  • @chris, I know static_assert is at compile time, that's exactly why I said that. – msknapp Sep 11 '14 at 04:13
  • if your IDE is only paying attention to the line inside the template where the error manifests and not giving you ready access to the calling site, reconfigure or get a better IDE. – Tony Delroy Sep 11 '14 at 04:14
  • @TonyD, I thought the IDE just echoes what the compiler tells it right? I'm using Netbeans because I hate how Microsoft always pressures you to buy their product, and I didn't like Code::Blocks when I tried it. – msknapp Sep 11 '14 at 04:15
  • @TonyD, you're right when you say I can accept a basic_string as the argument parameter, maybe my example is not great at demonstrating why you might want to restrict the template parameter, but I do believe there could be times when you want to use a template, but want to restrict the types it allows to certain classes, and get compilation errors in the right place. I'm sort of trying to figure out how I can use this later on. – msknapp Sep 11 '14 at 04:20
  • 1
    @msknapp: the IDE may selectively display just part of what the compiler's told it... run your compiler from the command line if you want to check. More generally, static assertions work reasonably well, as can `enable_if`, and there's something called "Concepts" in the pipeline for future C++ Standards that will address this much more squarely - you might like to google that and have a read about the motivations for it as the currently supported alternatives should be documented and contrasted. – Tony Delroy Sep 11 '14 at 04:26
  • @msknapp I don't believe you. The error message you've posted is because you're missing `std::`. Post an [SSCCE](http://sscce.org) to prove otherwise. – Praetorian Sep 11 '14 at 04:52
  • 1
    OMG, when I put that back in, the IDE still just highlights the static_assert line, but the console output DOES list line 22, where the real problem lies. I feel humiliated. Lesson learned: read the console/build output, the IDE does not highlight the line that calls that method. – msknapp Sep 11 '14 at 04:53
  • I think I will update the question because I had that 'std::' in the original code, but I just didn't look at the error output close enough. – msknapp Sep 11 '14 at 04:56

2 Answers2

2

This is what GCC prints:

prog.cc: In instantiation of 'const T first_numeric(const T&, const T&) [with T = std::basic_string<char>]':
prog.cc:16:64:   required from here
prog.cc:6:5: error: static assertion failed: You must use a number!
     static_assert(std::is_arithmetic<T>::value,"You must use a number!");
     ^

Note the prog.cc:16:64: required from here line? It tells you where the template is called.

Clang's error message is even clearer:

prog.cc:6:5: error: static_assert failed "You must use a number!"
    static_assert(std::is_arithmetic<T>::value,"You must use a number!");
    ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
prog.cc:16:47: note: in instantiation of function template specialization 'first_numeric<std::__1::basic_string<char> >' requested here
    std::cout << "first_numeric of x, y is "<<first_numeric(y,x)<<std::endl;
                                              ^

Some IDEs like Visual Studio truncates the error message in their "Errors" window, but the build output window on Visual Studio has the full error message:

[...]\consoleapplication2.cpp(10): error C2338: You must use a number!
          [...]\consoleapplication2.cpp(20) : see reference to function template instantiation 'T first_numeric<std::string>(const T &,const T &)' being compiled
          with
          [
              T=std::string
          ]

Line 20 in this case is the line in main() that calls first_numeric.

If you really really want the error to originate at the line calling the function, you can selectively disable the function template using std::enable_if or a similar mechanism, but usually this leads to less readable and harder to understand error messages. You'll typically get a "no matching function" error.

T.C.
  • 133,968
  • 17
  • 288
  • 421
0

After reading bjlee72's link I tried this:

template <class T> const T first_numeric(const T& first, const T& second);

template <> const int first_numeric<int>(const int& first, const int& second) {
    return first<second ? first : second;
}

int main(int argc, char** argv) {
//    int x = 3;
//    int y = 8;
//    std::cout << "first_numeric of x, y is "<<first_numeric(y,x)<<std::endl;
    std::string x = "3";
    std::string y = "8";
    std::cout << "first_numeric of x, y is "<<first_numeric(y,x)<<std::endl;
}

After switching it to the above, I re-built and the only compilation failure was on line 22, where I call the method, so I think this is a good solution.

Since I don't want to be a db, and since T.C.'s answer is great, I'm not going to pick this as the answer. Just wanted to add it here in case people want to see it.

Community
  • 1
  • 1
msknapp
  • 1,595
  • 7
  • 22
  • 39
  • 3
    This works, but you should simply add a non-template overload that takes two `int` arguments instead of specializing the function template. Read all about the potential pitfalls of function template specialization [here](http://www.gotw.ca/publications/mill17.htm). – Praetorian Sep 11 '14 at 04:54