0

For simplicity, I will omit things like proper typedefs to opaque structs instead of void *, Windows calling conventions, fixed integer types, etc.

Suppose I have the following files:

CApi.h -- Shared library header with C linkage for portability and hiding proprietary code.

#define LIB_API  // library import/export details

extern "C" {

typedef int error;

error LIB_API lib_foo_create(void ** foo);
error LIB_API lib_foo_destroy(void * foo);
error LIB_API lib_foo_func(void * foo, int * out);

error LIB_API lib_bar_create(void ** bar);
error LIB_API lib_bar_destroy(void * bar);
error LIB_API lib_bar_func(void * bar, int * out); // Suppose this internally uses Foo::func from CxxApi.hpp with C++17

} // extern C

CxxApi.hpp -- Header only wrapper to simplify API usage.

#include "CApi.h"

namespace lib {

namespace detail {

template < typename Return = void, typename Func, typename... Args >
Return c_api(Func func)
{
    // Not sure how this affects ODR if client and provider code use different versions,
    // but neither see each other's code usage.

    #if __cplusplus >= 201703L // C++17
    // more efficient implmentation (e.g. fold expressions)
    #else
    // fallback implmentation (e.g. recursion)
    #endif
}

} // namespace lib::detail

class Foo
{
public:
    Foo() { detail::c_api(lib_foo_create, &handle_); }

    ~Foo() { detail::c_api(lib_foo_destroy, handle_); }

    int func() { return detail::c_api<int>(lib_foo_func, handle_); }

private:
    void * handle_;
};

struct Bar
{
public:
    Bar() { detail::c_api(lib_bar_create, &handle_); }

    ~Bar() { detail::c_api(lib_bar_destroy, handle_); }

    int func() { return detail::c_api<int>(lib_bar_func, handle_); }

private:
    void * handle_;
};

} // namespace lib

If I, the API provider, compile this using C++17, but the client using the API uses C++11, is ODR violated with lib::detail::c_api? I believe it is not because lib_bar_func's definition is in a different translation unit than client code, but I am not positive.

Matt Eding
  • 917
  • 1
  • 8
  • 15
  • Formally, this is ODR violation, though chances are high it will work in practice. "**[basic.def.odr]/6** There can be more than one definition of a ... non-static function template ... in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named `D` defined in more than one translation unit, then (6.1) — each definition of `D` shall consist of the same sequence of tokens..." – Igor Tandetnik Dec 31 '22 at 23:06
  • That said, just by linking together translation units compiled by (in effect) different compilers, you are already in implementation-defined territory. The standard doesn't contemplate a program like that. So you can lean on implementation-specific guarantees, such as ABI. Since two versions of the function have the same signature their bodies should be interchangeable under ABI (assuming they produce the same result and have the same side effects, of course). – Igor Tandetnik Dec 31 '22 at 23:08
  • @IgorTandetnik I suppose this could be better suited to use inline namespaces based on the C++ version? That way if there is a versioning conflict, at worst a nearly identical template is stamped out. As for your ABI linkage comment, I am using MSVC and know its 2015-2022 runtime has guaranteed binary compatibility (provided preconditions are met). – Matt Eding Jan 01 '23 at 01:20

0 Answers0