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?
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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With