1

Compiling and linking my only file defining a custom literal fails. The file consists of the custom literal definition (operator"") and before that there is a template class transforming digits (typename ... Chars) into a number of type NumberT:

#include <cstdint>
#include <chrono>
#include <limits>


using Du = std::chrono::duration<uint16_t>;


template <typename NumberT, size_t Depth, char ... String>
struct _StringToNumber;

template <typename NumberT, size_t Depth, char Head, char ... Tail>
struct _StringToNumber<NumberT, Depth, Head, Tail ...> {
    static_assert('0' <= Head <= '9', "unsupported character in unsigned number literal");

    using next = _StringToNumber<NumberT, Depth+1, Tail ...>;

    const static size_t total_depth = next::total_depth;

    const static NumberT order_value = (total_depth-Depth-1)*(Head - '0');

    static_assert(std::numeric_limits<NumberT>::max() - next::value >= order_value, "literal does not fit the underlying type");

    const static NumberT value =  order_value + next::value;

};

template <typename NumberT, size_t Depth>
struct _StringToNumber<NumberT, Depth> {
    const static size_t total_depth = Depth;
    const static NumberT value = 0;
};

template <typename NumberT, char ... Chars>
using StringToNumber = _StringToNumber<NumberT, 0, Chars ...>;


template <char ... Chars>
Du operator "" _du () {  // my custom literal
    return Du(StringToNumber<Du::rep, Chars ...>::value);
}


int main() {
    1_du;  // Undefined symbols for architecture x86_64: "_StringToNumber<unsigned short, 0ul, (char)49>::value"
    StringToNumber<uint16_t, '2'>::value;  // apparently works

    return 0;
}

Linker error:

Undefined symbols for architecture x86_64:
  "_StringToNumber<unsigned short, 0ul, (char)49>::value", referenced from:
      std::__1::chrono::duration<unsigned short, std::__1::ratio<1l, 1l> > operator"" _du<(char)49>() in scratch_1-71f359.o
ld: symbol(s) not found for architecture x86_64

strangely I don't get the error if I substitute Du and Du::rep with e. g. uint16_t.

command g++ -std=c++17 thefile.cpp

$ g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 10.0.0 (clang-1000.11.45.5)
Target: x86_64-apple-darwin18.0.0
Thread model: posix
Adam
  • 1,724
  • 4
  • 21
  • 31

1 Answers1

2

The below syntax within the definition of struct:

const static NumberT value = 0;

is a declaration with an initializer, not a definition. That is, the compiler may use its value for optimization purposes, but once value is ODR-used (e.g., bound to a reference), that entity must have an address in memory. By using std::chrono::duration as a result of operator"", you force value to be bound by a reference that duration's constructor accepts as a parameter, hence the linker is allowed to complain about the missing definition. In order to provide a definition, put the following lines after the definition of the struct itself:

template <typename NumberT, size_t Depth>
const NumberT _StringToNumber<NumberT, Depth>::value;

and after the specialization:

template <typename NumberT, size_t Depth, char Head, char ... Tail>
const NumberT _StringToNumber<NumberT, Depth, Head, Tail ...>::value;

Or make all of the declarations inline ():

template <typename NumberT, size_t Depth>
struct _StringToNumber<NumberT, Depth> {
    inline const static size_t total_depth = Depth;
    inline const static NumberT value = 0;
};

Also note that identifiers starting with an underscore followed by an upper-case letter are reserved for the implementation.

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160