5

edit This is not a duplicate of Undefined reference to static class member. That question explored the cause of the problem (which I explain below). Here, I'm looking for a different solution from those proposed in the answers to that questions (which implied changing the declaration/definition of the constexpr variable to be used -- essentially by adding a definition in a compilation unit).

I have created a little variadic template function make_string() to generate a std::string from any number of io-able arguments as follows.

using std::ostringstream; // just for this example

inline ostringstream&write(ostringstream&ostr, const char*x)
{ if(x) ostr<<x;  return ostr; }

template<class T>
inline ostringstream&write(ostringstream&ostr, T const&x)
{ ostr<<x;  return ostr; }

inline ostringstream&write(ostringstream&ostr) noexcept
{ return ostr; }

template<class T, class... R>
inline ostringstream&write(ostringstream&ostr, T const&x, R&&... r)
{ return write(write(ostr,x), std::forward<R>(r)...); }

inline std::string make_string(const char*text)
{ return {text?text:""}; }

inline std::string make_string(std::string const&text)
{ return {text}; }

template<typename T>
inline auto make_string(T var) -> decltype(std::to_string(var))
{ return std::to_string(var); }

template<class... Args>
inline std::string make_string(Args&&... args)
{
  ostringstream ostr;
  write(ostr,std::forward<Args>(args)...);
  return std::move(ostr.str());
}

Now, this works pretty well and can be used like this

throw std::runtime_error(make_string("offset=",offset," > max_offset =",
                                      max_offset"));

However, there is a problem when printing static constexpr class members, as in

class foo
{
   static constexpr int max_offset=some_value;
   // ...
   void bar(int offset)
   {
     if(offset > max_offset)
     throw std::runtime_error(make_string("offset=",offset," > max_offset=",
                                          max_offset"));
   }
};

This causes an error at link time. The reason is that make_string takes all its arguments by reference, including the static constexpr max_offset. As a result, a reference to foo::max_offset will be required at linking, see also.

How can I avoid this problem without abandoning the idea of make_string()? (Perhaps one could replace the variadic template with a variadic macro, but I would consider this as some sort of regression.) There must be a way for make_string to take its arguments by value or reference, depending on type (so that builtin types can be taken by value). How?

Community
  • 1
  • 1
Walter
  • 44,150
  • 20
  • 113
  • 196
  • possible duplicate of [Undefined reference to static class member](http://stackoverflow.com/questions/272900/undefined-reference-to-static-class-member) – Mark Garcia Mar 14 '14 at 09:35
  • @MarkGarcia Not so. I don't want to know the cause of the problem, but I ask whether the problem can be solved in a certain way not explored in the answers to that question. – Walter Mar 14 '14 at 09:42
  • What compiler are you using? Recent clang and gcc have no problem with this. – jrok Mar 14 '14 at 09:59
  • @jrok Yes, I'm using clang 3.4 and gcc 4.8.1, but with a more complex example than that above, spread over several compilation units. – Walter Mar 14 '14 at 10:25
  • Possible duplicate of [passing a static constexpr variable by universal reference?](http://stackoverflow.com/questions/22172789/passing-a-static-constexpr-variable-by-universal-reference). In particular, I think the entire `make_string` application is irrelevant, the problem can be exhibited with much simpler code. Note that the chosen answer is not my preferred solution, as discussed below it. There are other solutions, I can try an answer in a few hours along with suggestions for a better `make_string` (goto to go now). – iavr Mar 14 '14 at 10:38
  • Also related: http://stackoverflow.com/q/22867654/103167 – Ben Voigt Apr 04 '14 at 17:08

2 Answers2

3

I'm not sure whether the compiler is correct in getting it's knickers in a bunch jimmies rustled with a ref to constexpr here.

However, you could perhaps find your way out using boost's

  • call_traits<T>::param_type

    Defines a type that represents the "best" way to pass a parameter of type T to a function.

(see http://www.boost.org/doc/libs/1_55_0/libs/utility/call_traits.htm).

sehe
  • 374,641
  • 47
  • 450
  • 633
  • I don't understand your last paragraph. In my code, only one (not many) `ostringstream` is instantiated in creating a `std::string`. My function `write()` does exactly what you said: returning a `ostringstream`. Moreover, when throwing an exception, you most likely won't reuse anything. – Walter Mar 14 '14 at 10:16
  • Sorry, I missed the distinction between `make_string` and `write` there. You're right. Conceptually, though, when an exception is in flight is ***exactly*** the time to avoid doing allocations (this is why `std::exception::what()` is no-throw **and** returns `const char*` – sehe Mar 14 '14 at 10:18
  • Yes, but how can one assemble a meaning full error message otherwise? One could use some threadlocal statically reserved memory and `snprintf` -- is that what you recommend? – Walter Mar 14 '14 at 10:27
  • Or have a threadlocal, statically initialized stringstream (properly reserving enough buffer space) :) I know, you'd have to lazily construct it (which is what I do in my code base). (**Edit** I actually removed that last paragraph, I forgot - kinda busy) – sehe Mar 14 '14 at 10:32
  • That sounds interesting. What I don't understand is how you lazily construct it. Do you mean that your (equivalent to) `make_string` instead of initiating a new `ostringstream` simply `clear()`s its thread local static object and then writes into that? – Walter Mar 14 '14 at 10:33
  • Back to the original problem. I cannot see how one can use `call_traits::param_type` here. Can you explain that a bit more? Also, `call_traits::param_type` is `const int&`, not `const int`, so this may not help. – Walter Mar 14 '14 at 10:39
  • An example: `THREAD_LOCAL auto os = std::make_unique();` (To reserve size, initialize from a lambda instead. Then, `os->str().clear(); os->clear();` at strategic points). IIRC only MSVC actually required this because they don't fully support C++11 `thread_local` (but my memory is a bit hazy on this detail) – sehe Mar 14 '14 at 10:40
  • I might find the time to show what I meant here. For now, the idea is roughly: `make_string(Args... args) { /*...*/ write(ostr, static_cast::param_type >(args)...); }` – sehe Mar 14 '14 at 10:47
2

First, I am not sure why you need so much code for make_string. I'd simply define it as

template<class... Args>
inline std::string make_string(Args&&... args)
{
  ostringstream ostr;
  _do{ostr << std::forward<Args>(args)...};
  return std::move(ostr.str());
}

where

struct _do { template <typename... T> _do(T&&...) { } };

is a helper struct that lets you evaluate expressions in the right order (but watch out, GCC incorrectly evaluates right-to-left until 4.9 at least).


Now, to your question. As I said in my comment, I feel your problem is irrelevant to make_string. In Undefined reference to static class member, in my question passing a static constexpr variable by universal reference?, and in all relevant questions I've seen, the suggested answer is that one defines the variable somewhere out of class:

constexpr int foo::max_offset;

I'm not sure if this is a problem for you. It is a problem for me because in heavily templated code it implies too much duplication (see discussion below my question). Anyhow, if it is a problem, I see a few other simple solutions to ensure call-by-value:

  • use make_string(..., int(max_offset)) instead of make_string(..., max_offset)

  • as a shortcut, +max_offset does the same job (suggested here)

  • define static constexpr int max_offset() { return some_value; }, then use max_offset() instead of max_offset throughout

  • let some part of code (function or template) deduce max_offset as a non-type int template parameter, then use it directly

  • lastly, define make_string(Args... args) (this is the simplest but does not apply here as you don't want to copy all those strings)

I am not discussing use of make_string in throwing an exception; this is a different problem.

Community
  • 1
  • 1
iavr
  • 7,547
  • 1
  • 18
  • 53
  • Good. for the time being I'll use your `+max_offset` solution. I'm interested in your `struct _do`. Where does this type of magic come from? Is there some online discussion/docu for that? Could one not "repair" the bug with GCC (<4.9)? – Walter Mar 17 '14 at 13:11
  • @Walter To be fair, I saw unary `+` solution [here](http://stackoverflow.com/a/272996/2644390) (updated link in answer). Now, `_do`: I first saw it when familiarizing with [variadic templates in Wikipedia](http://en.wikipedia.org/wiki/Variadic_template) (look for `struct pass`), then again in STL implementations, usually called `swallow` (as in the GCC bug report). It is based on the requirement that in list initialization, arguments are evaluated in order of appearance (left-to-right). The only document I know is [this](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1030). – iavr Mar 17 '14 at 14:48
  • @Walter As for GCC bug, maybe I was not clear but I meant that (unfortunately) the bug still persists in 4.9. I don't know more and it's not easy to find (try searching). For many other reasons, I consider clang my primary compiler, so I only use GCC for cross-checking. I only hope it will be fixed eventually. – iavr Mar 17 '14 at 14:53
  • This is very odd. How come such a serious bug is persists over more than two years and over many compiler upgrades? – Walter Mar 19 '14 at 09:06
  • Why do you think left to right is mandated here? It is not an initializer list. – Yakk - Adam Nevraumont Apr 04 '14 at 20:34
  • @Yakk `_do{args...};` is an initialization of an unnamed temporary with a *braced-init-list*, so 8.5.4 paragraph 4 applies, right? – iavr Apr 04 '14 at 21:41
  • @Yakk The testcase of [gcc bug 51253](http://gcc.gnu.org/bugzilla/show_bug.cgi?id=51253) still produces `20` in version 4.8.1; clang produces `21`. – iavr Apr 04 '14 at 21:44
  • @Yakk Here's a live example for [gcc](http://coliru.stacked-crooked.com/a/c87a94584bb7db40) and [clang](http://coliru.stacked-crooked.com/a/116391f92c850380). – iavr Apr 04 '14 at 21:48
  • http://stackoverflow.com/questions/21234380/object-creation-order-in-braced-init-list -- ya, you are right. I misremembered it as being only for `initializer_list` cases, not `{` initializer-list `}`, if you get my misunderstanding. – Yakk - Adam Nevraumont Apr 04 '14 at 22:53