Boost logo

Boost :

Subject: Re: [boost] [Review] Boost.Convert library, last day
From: John Bytheway (jbytheway+boost_at_[hidden])
Date: 2011-05-04 05:23:18


On 03/05/11 21:28, Matthew Chambers wrote:
<snip>
> I use an optional header that defines lexical_cast string->numeric
> specializations using strto[ld]. Is this at risk for ODR violations? I
> use it widely but I'm not at all careful about making sure it's included
> everywhere. Yet I haven't had any ODR violations on GCC or MSVC. Unless
> that's pure luck, I'd like to know how the optional specializations are
> ODR violations.

The thing about ODR violations is that most of the time your program
will work fine despite containing them, and it's practically impossible
for a compiler to detect them (I think the very latest versions of gcc
make some attempt, but not much). So, you might easily have them and
not know. On the one hand, the fact that they rarely break the program
means that we might be somewhat blasé and not worry about them, but on
the other hand I certainly think Boost should not promote deliberate
violations of the standard, and I suspect these things might lead to
more problems with whole-program optimization.

If you would like a concrete example, here's one. Suppose we're
implementing a C++ version of the Python repr functionality (called,
perhaps foolishly, "print", here). We have a default implementation
which simply prints out the typeid and address of the object, and it can
be specialised for specific types where appropriate.

I have written two functions, f() and g(), both of which print out the
integer 0, but g does it in the presence of a specialization, and f does
not.

(I've had to use "__attribute__((noinline))" in this code to make the
badness obvious, but of course the compiler is always at liberty to not
inline the function, so the badness was always there)

===== print.hpp
#include <cstdio>
#include <typeinfo>

template<typename T>
struct print_helper {
  void operator()(T const& t) {
    std::printf("<%s at %p>\n", typeid(T).name(), &t);
  }
};

template<typename T>
__attribute__((noinline)) void print(T const& t)
{
  print_helper<T>()(t);
}

void f();
void g();

===== f.cpp
#include "print.hpp"

void f()
{
  print(0);
}

===== g.cpp
#include "print.hpp"

template<>
struct print_helper<int>
{
  void operator()(int const& t) {
    std::printf("int(%d)\n", t);
  }
};

void g()
{
  print(0);
}

===== main.cpp
#include "print.hpp"

int main()
{
  f();
  g();
  return 0;
}

There is an ODR violation in this program because there are two
definitions of print<int>, and they violate N3092 [basic.def.odr] p7
point 2, because the name "print_helper<T>" refers to different entities
in the two definitions. This ODR violation does indeed lead to
undefined behaviour; observe what happens when I compile and run this
program:

$ g++ -o odr main.cpp f.cpp g.cpp
$ ./odr
<i at 0x7fff0d49ef8c>
<i at 0x7fff0d49ef8c>
$ g++ -o odr main.cpp g.cpp f.cpp
$ ./odr
int(0)
int(0)

It does different things according the the order in which I pass the
source files on the command line. This is because the two (different)
definitions of print<int> are being merged into one.

There may or may not be a second ODR violation because of the two
different definitions of print_helper<int>. My standard-ese is
insufficient to figure out whether this is a violation or not.

But even this first problem could clearly arise if e.g. library A which
uses Boost.Convert in a template function to perform a conversion which
is specialised by library B which depends on library A. This is very
difficult to detect and avoid.

John Bytheway


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