Frage

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?

War es hilfreich?

Lösung 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.

Andere Tipps

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).

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top