Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

variadic template arguments: can I pick reference vs value depending on type?

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?

like image 713
Walter Avatar asked Mar 14 '14 09:03

Walter


2 Answers

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

like image 76
sehe Avatar answered Nov 01 '22 20:11

sehe


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.

like image 37
iavr Avatar answered Nov 01 '22 20:11

iavr