0

I am playing around with converting standard library functions like stoi to not throw exceptions on failures but keep running into linking issues. I followed steps from other similar questions but the linker is still not satisfied

Header.h

#include <optional>
#include <iostream>
#include <string>
#include <cstddef>
namespace temp{
    template<class T>
    std::optional<int> noexcept_stoi( const T& str, std::size_t* pos = nullptr, int base = 10)
    {
      //body of the function 
    }
    template<> inline std::optional<int> noexcept_stoi<std::wstring>( const std::wstring& str, std::size_t* pos, int base );
    template<> inline std::optional<int> noexcept_stoi<std::string>( const std::string& str, std::size_t* pos, int base );
};

Test.cpp

#include "Header.h"
int main(){
  std::string input = "123";
  auto result = temp::safe_stoi(input);
return 0;
}

Error: Undefined symbols for architecture arm64: "std::__1::optional temp::noexcept_stoi<std::__1::basic_string<char, std::__1::char_traits, std::__1::allocator > >(std::__1::basic_string<char, std::__1::char_traits, std::__1::allocator > const&, unsigned long*, int)"

What am I missing ?

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Kish
  • 293
  • 1
  • 6
  • 3
    You just declared specializations, not defined them. – 273K May 23 '22 at 01:26
  • @AnoopRana Why do you think the OP wants explicit template instantiations? Their question title says "template specialization", and their syntax is a template specialization. It's not an *unreasonable* assumption, but in that case your answer should explain the difference between the two things, primarily, to clear up the confusion. – Sebastian Redl May 23 '22 at 07:20
  • @SebastianRedl I have updated my answer to include both ways of solving the problem. – Jason May 23 '22 at 07:29

1 Answers1

2

The problem is that you have only provided the declarations for the specializations and not the corresponding definition. There are two ways to solve this as given below:

Method 1

Here we provide the corresponding definition for the specializations.

namespace temp{
    template<class T>
    std::optional<int> noexcept_stoi( const T& str, std::size_t* pos = nullptr, int base = 10)
    {
      return 4; //don't forget to return something
    }
    template<> inline std::optional<int> noexcept_stoi<std::wstring>( const std::wstring& str, std::size_t* pos, int base )
    {
        return 4;
    }
    template<> inline std::optional<int> noexcept_stoi<std::string>( const std::string& str, std::size_t* pos, int base )
    {
        return 4;
    }
};

Working demo

Method 2

We could make use of explicit template instantiation to solve this problem as shown below.

Header.h

#ifndef MYHEADER_H
#define MYHEADER_H
#include <optional>
#include <iostream>
#include <string>
#include <cstddef>
namespace temp{
    template<class T>
    std::optional<int> noexcept_stoi( const T& str, std::size_t* pos = nullptr, int base = 10)
    {
      return 4; //return something
    }
//-----------------v------------------------->no need for angle brackets as well as inline 
    extern template  std::optional<int> noexcept_stoi<std::wstring>( const std::wstring& str, std::size_t* pos, int base );
//-----------------v------------------------->no need for angle brackets as well as inline
    extern template  std::optional<int> noexcept_stoi<std::string>( const std::string& str, std::size_t* pos, int base );
};

#endif

main.cpp


#include <iostream>
#include "Header.h"
//explicit template instantiation definition
template  std::optional<int> temp::noexcept_stoi<std::wstring>( const std::wstring& str, std::size_t* pos, int base );
template  std::optional<int> temp::noexcept_stoi<std::string>( const std::string& str, std::size_t* pos, int base );
int main(){
  std::string input = "123";

  auto result = temp::noexcept_stoi(input);
return 0;
}

Working demo

Jason
  • 36,170
  • 5
  • 26
  • 60
  • TIL. I was absolutely sure that explicit instantiations counted as ordinary functions for this purpose. Seems I was wrong. But I strongly question the *point* of putting explicit instantiation definitions (as opposed to declarations that start with `extern`) into headers. That would actually increase the compiler's work, not decrease it. – Sebastian Redl May 23 '22 at 08:03
  • Gents, can you point the exact point in the standard that allows it? – alagner May 23 '22 at 08:13
  • OK, I've done some more reading: my main concenrn was this part (in method 2) `template std::optional noexcept_stoi( const std::wstring& str, std::size_t* pos, int base );` possibly violating ODR, but it's no problem due to `noexcept_stoi` being inline, is that the case? – alagner May 23 '22 at 08:50
  • 1
    @SebastianRedl Actually if the header is included in more than one source files we will have UB. From [cppreference](https://en.cppreference.com/w/cpp/language/class_template): *"An explicit instantiation definition forces instantiation of the class, struct, or union they refer to. It may appear in the program anywhere after the template definition, and for a given argument-list, is only allowed to appear once in the entire program, no diagnostic required."* I have used `extern` in my updated method 2. – Jason May 23 '22 at 09:42
  • 1
    @alagner If the header in method 2 is included in more than one source file then it will be ill-formed. So i have updated method2 to use explicit template instantiation declaration using `extern`. From [cppreference](https://en.cppreference.com/w/cpp/language/class_template) *"An explicit instantiation definition forces instantiation of the class, struct, or union they refer to. It may appear in the program anywhere after the template definition, and for a given argument-list, is only allowed to appear once in the entire program, no diagnostic required."* – Jason May 23 '22 at 09:43
  • In N4901, that's 13.9.1/5. Nice to see that I did remember the rules correctly, even if I didn't remember the consequences correctly. – Sebastian Redl May 23 '22 at 11:11