Boost logo

Boost :

Subject: Re: [boost] C++03 / C++11 compatibility question for compiled libraries
From: Raffi Enficiaud (raffi.enficiaud_at_[hidden])
Date: 2018-02-12 08:45:21


Le 10.02.18 à 19:09, Steven Watanabe via Boost a écrit :
> AMDG
>
> On 02/10/2018 03:32 AM, Raffi Enficiaud via Boost wrote:
>> Le 09.02.18 à 20:08, Steven Watanabe via Boost a écrit :
>>> <snip>
>>> In general, I think that:
>>> - interfaces that take boost/std::xxx as a parameter
>>> should accept both.> - interfaces that return boost/std::xxx are hard
>>> and I don't know how to handle them.
>>
>> Yes. You are starting/proposing a recipe for ABI compatibility, which is
>> the target of my concerns. Now:
>>
>> * do you think we can elaborate more on this? For instance, same type of
>> problem should apply to template parameters as well, exceptions, new
>> keywords...
>
> template parameters are not a major issue,
> as you can just accept whatever the user gives
> you. If you're explicitly instantiating the
> template in the separately compiled library,
> it should be safe to instantiate both versions,
> and the one that isn't used will just be ignored
template <class some_struct> class my_template;

If some_struct is a class, it should be the same in every translation
units, isn't it. Same more or less for my_template. By induction,
everything that is pulled by those 2 entities should have the same
implementation if they have exposed/public symbol and they land in
different compilation units. In the case where this template + template
parameter is seen in two different shared or static libraries, I believe
they share the same symbol, while maybe implementing something different.

That means
* enforcing the use of the same language variant for the compilation of
a library and all its dependencies. I believe this is what is done in
the Debian community (please correct me if needed)
* or enforce the ABI compatibility, which is difficult for developers,
as the language does not provide enough semantics on this, and a lot of
know-how seems to be compiler or platform specific.
* or carefully crafting the visibility of each of those symbols, to
lower the risk of having the same symbol with different definitions,
which let the problem of static libraries the same

> It's probably okay to throw different exceptions
> as the default assumption in C++ is that any function
> can throw any exception.

Exceptions or return type, the problem isn't it the same? the exception
thrown should be ABI compatible, isn't it? Moreover, exceptions should
have public visibility I believe.

> A function that has the same mangled name in
> different standards and is defined in multiple
> translation units (i.e. inline functions or
> function templates) must be identical regardless
> of the standard.

Do you know if at least the linker emits an error or a warning if, same
symbol exists but with different definition? I really have not a single
clue, but it seems that this is a grey area where people just "hope" as
you said and have little control.

> Note that to be strictly correct
> it is not enough for the function to have the
> same effective behavior, as the optimizer is
> permitted to make assumptions based on the actual
> implementation. Also note that this is the hardest
> thing to get right and most code that tries to be
> ABI compatible across multiple standards tends to
> fudge it and hope for the best.

...

> If a function takes different parameters depending
> on the language standard, then it is a different
> function and both versions can coexist.
>
>> * do you think we can set up mechanisms that checks we are doing it
>> right? (apart from trial and error, which has an exponential complexity
>> in the number of types). I am just unaware of any tool or methodology
>> that may help me doing it right.
>>
>
> Just build the library and test case with different
> standards. If it works, it's probably good enough.

Well, the second best to hope for is a compiler or a linker error. The
worse is exactly something that presumably "works" with silent problems
that, for instance, corrupt a stack.

I was hoping for the second best.

>>> - The internal implementation can pick one or the
>>> other. If you want ABI compatibility between C++03
>>> and C++11, and the object crosses an ABI boundary,
>>> that means always using boost::xxx.
>>
>> I was hoping for another solution ... To me, that means that long run,
>> boost is implementing a side, non-compatible, C++ standard.
>>
>
> Pick your poison. It's impossible to use only std::xxx
> in the ABI while still being link-compatible with C++03.
> Personally, I can live with requiring a consistent std version,
> in which case, this is not a problem.

>From all this conversation, I learned at least

* that there is a real problem that is not just between the keyboard and
the chair (that is good for my ego ...)
* cross dialect APIs should be avoided, which was not obvious
* having API on different dialect is ok as long as it is a partition of
the API,
* that static or shared libraries naming conventions might help the user
avoiding mistakes, but means that all dialect variant should appear in
the name

This is what I think is a partition:

///////
  class helper_cpp03 {
      void somemethod(boost::function< void () > f);
  };

  #if CPP11
  class cpp11 {
      template <class T>
      void somemethod2(T&& v) {
         helper_cpp03 inst;
         // bind can talk to boost::function
         inst.somemethod( std::bind(&X::C, std::forward(v)) );
      }
  };
  #endif
///////
The two APIs can talk together, no symbol is modified depending on the
dialect. The API in the C++11 case is a superset of the one in C++03, I
can only add symbols, not modify C++03 existing ones.

This is not a partition:
///////
  class helper {
      #if CPP11
      void somemethod(std::function< void () > f);
      #else
      void somemethod(boost::function< void () > f);
      #endif
  };

  class myclass {
      template <class T>
      #if CPP11
      void somemethod2(T && v) {
          inst.somemethod( std::bind(&X::C, std::forward(v)) );
      }
      #else
      void somemethod2(T const & v) {
         inst.somemethod( boost::bind(&X::C, v) );
      }
      #endif
  };
///////

While this is a bit exaggerated in the example, I think this type of
code might happen: code is changed depending on the dialect:

* the interface is changed
* the way those two entities exchange data is changed (boost::bind vs
std::bind)

Does this make sense?

>
>>>
>>> For Boost.Test my overall recommendation is:
>>> always use boost::function, but use std::bind
>>> when it is available.
>>>
>>
>> The little story behind boost::bind was that, I believe around ~1.60, I
>> encountered issues between std::bind and boost::function for moveable
>> parameters. Maybe I was doing it wrong at that time, now it seems to be
>> fixed (I tested). But I think the problem remains.
>>
>
> In the worst case, you can use
> std::bind -> std::function -> boost::function.

This initial problem is fine now, it works as many other mentioned,
std::bind being fully compatible with boost::function (which I was not
aware of).


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk