0

the solution to this is probably trivial, but I can't find it. I tried to google it but with no luck.

I'm working on a C++ project using g++ on linux (gcc version 10.1.0 Ubuntu 10.1.0-2ubuntu1-18.04).

g++ compiles a C++ file into an object .o without raising any error, but the end object file is missing a function! The other 8 library files that I wrote are all compiled and linked fine, only this one is giving me trouble. Why, and how do I solve it?

The library header file bpo_interface.h is:

#pragma once

#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <boost/algorithm/string.hpp>

#include <optional>
#include <string>

namespace bpo = boost::program_options;


namespace ibsimu_client::bpo_interface {
    template <typename T>
    std::optional<T> get(bpo::variables_map &params_op, std::string key)

}

The bpo_interface.cpp:

#include "bpo_interface.h"

namespace ic_bpo = ibsimu_client::bpo_interface;

template <typename T>
std::optional<T> ic_bpo::get(bpo::variables_map &params_op, std::string key)
{
    try {
        const T& value = 
            params_op[key].as<T>();
        return value;
    } 
    catch(const std::exception& e) {
        return std::nullopt;
    }

    return std::nullopt;
}

The g++ command used to compile the file:

g++-10 -std=c++20 -lboost_program_options -Wall -g `pkg-config --cflags ibsimu-1.0.6dev` -c -o bin/build/bpo_interface.o src/bpo_interface.cpp

and the output of objdump -t -C bin/build/bpo_interface.o:

bin/build/bpo_interface.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 bpo_interface.cpp
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss   0000000000000000 .bss
0000000000000000 l    d  .rodata    0000000000000000 .rodata
0000000000000000 l     O .rodata    0000000000000001 __pstl::execution::v1::seq
0000000000000001 l     O .rodata    0000000000000001 __pstl::execution::v1::par
0000000000000002 l     O .rodata    0000000000000001 __pstl::execution::v1::par_unseq
0000000000000003 l     O .rodata    0000000000000001 __pstl::execution::v1::unseq
0000000000000004 l     O .rodata    0000000000000004 __gnu_cxx::__default_lock_policy
0000000000000008 l     O .rodata    0000000000000008 boost::container::ADP_nodes_per_block
0000000000000010 l     O .rodata    0000000000000008 boost::container::ADP_max_free_blocks
0000000000000018 l     O .rodata    0000000000000008 boost::container::ADP_overhead_percent
0000000000000020 l     O .rodata    0000000000000008 boost::container::ADP_only_alignment
0000000000000028 l     O .rodata    0000000000000008 boost::container::NodeAlloc_nodes_per_block
0000000000000030 l     O .rodata    0000000000000001 boost::container::ordered_range
0000000000000031 l     O .rodata    0000000000000001 boost::container::ordered_unique_range
0000000000000032 l     O .rodata    0000000000000001 boost::container::default_init
0000000000000033 l     O .rodata    0000000000000001 boost::container::value_init
0000000000000000 l    d  .debug_info    0000000000000000 .debug_info
0000000000000000 l    d  .debug_abbrev  0000000000000000 .debug_abbrev
0000000000000000 l    d  .debug_aranges 0000000000000000 .debug_aranges
0000000000000000 l    d  .debug_line    0000000000000000 .debug_line
0000000000000000 l    d  .debug_str 0000000000000000 .debug_str
0000000000000000 l    d  .note.GNU-stack    0000000000000000 .note.GNU-stack
0000000000000000 l    d  .comment   0000000000000000 .comment

Coherently with the objdump result, the linker complains that it cannot find the ic_bpo::get() function - specifically:

undefined reference to 'std::optional<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > ibsimu_client::bpo_interface::get<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(boost::program_options::variable_maps&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)

If I copy&paste the function body into the definition file bpo_interface.h and remove the bpo_interface.cpp and bpo_interface.o from the project, everything works fine.

So I guess g++ at compile time is perfectly able to process that function and match its declaration with its use in the project.

But why is not compiled into the bpo_interface.o object file?

Thank you

einpoklum
  • 118,144
  • 57
  • 340
  • 684
Duccio
  • 143
  • 2
  • 7

2 Answers2

0

You have the placed the definition of the template function get() in a source (cpp) file. This means the definition is not available when needed for instantiations of the function template for particular specializations outside of this source file. Note that the definition of a function template is not equivalent to the definition of a non-function template, it is more like a blueprint for how to generate definitions for particular specializations; as needed for implicit or explicit instantiation (definitions).

When moving the definition to the header file, the function template definition is readily available as needed when particular specializations are instantiated. You can place the definition of a function template in a source file, but as that source file is then the only translation unit that can see the definitions, you would also need to provide explicit instantiation definitions of all the specializations you would like the template function to provide instantiated definitions for. This is quite uncommon, and usually only used for e.g. static dependency injection into class template which have only a single specialization used for production code intent (which can can then be explicitly instantiated) and e.g. other instantiations for test code (e.g. injecting mocked or stubbed implementations).


But why is not compiled into the bpo_interface.o object file?

From cppreference - function templates [emphasis mine]:

Function template instantiation

A function template by itself is not a type, or a function, or any other entity. No code is generated from a source file that contains only template definitions. In order for any code to appear, a template must be instantiated: the template arguments must be determined so that the compiler can generate an actual function (or class, from a class template).

dfrib
  • 70,367
  • 12
  • 127
  • 192
0

tl;dr: Add the following to your .cpp file:

template std::optional<std::string> ic_bpo::get<std::string(bpo::variables_map &, std::string);

(and of course make sure to include the <string> header.)


But why is not compiled into the bpo_interface.o object file?

Because you defined a template; you did not instantiate that template at all. Only instantiations are actual functions, which can be compiled and put in object files. So, you need to force an instantiation of your template; that's what the line above does, for the case of T = std::string.

Alternatively, if you keep the template definition in your header, than other translation units can instantiate it themselves as needed.

See also:

Explicit template instantiation - when is it used?

einpoklum
  • 118,144
  • 57
  • 340
  • 684