String-formatting and printing functions
As you mention, the simple replacing is susceptible to problems if a replacement string includes a format specifier. But setting that aside, there are still a few things I would rather do differently.

Document. You should be sure to document your formatting language in comments; without reading the function, or the example in main, it's impossible to know that the formatting language uses curly braces around indices starting at zero, whether you can repeat or skip indices, or what happens if you provide an out-of-bounds index.

Avoid C functions? This may be personal style, but I would prefer to avoid snprintf in format_impl. I would instead probably use something like "{" + std::to_string(N) + "}". The resulting local could then be marked const, or the expression could go directly in the call to boost::replace_all.

Remove redundancy. Jeffrey's comment about std::string&amp; and std::string&amp;&amp; overloads is half right. It should be relatively rare that you need to provide both overloads. In places that are doing template type deduction on parameter T, T&amp;&amp; can be deduced as either type. Here there is no deduction so that cannot happen. (That's the part of the comment that was half wrong - it didn't apply here.)

Instead we have to look at how fmt is being used. In format(const std::string&amp;, ...) this immediately makes a mutable copy called temp. In format(std::string&amp;&amp;, ...), this moves the local to another mutable copy called temp. From there, both functions are identical. I assert that there should only be one function here.

What function? Well, you want to modify the parameter, so it needs to be mutable. If the parameter is not mutable, you'll have to make a copy. So that rules out a const std::string&amp;. And as your example shows, you can't always convert to a std::string&amp;&amp;. So what's the right option? Plan vanilla std::string. If you call it with a const, the caller makes a copy; the function receives a copy that it is free to mutate. If you call it with an rvalue, the caller moves it and the function receives a copy that it is free to mutate. So the result?

template &lt;class... Args&gt;
std::string format(std::string fmt, Args&amp;&amp;... args)
{
    detail::format_impl&lt;0&gt;(fmt, std::forward&lt;Args&gt;(args)...);   
    return fmt;
}


The same argument applies to print very similarly. The only question is if you'll want to explicitly mark the string it passes to format with a std::move, or trust your compiler to figure it out. (In this case it's simple enough I'd probably leave it out. But it does serve as implicit documentation to a future reader, so I'll show it here.)

template &lt;class... Args&gt;
void print(std::string fmt, Args&amp;&amp;... args)
{
    std::cout &lt;&lt; format(std::move(fmt), std::forward&lt;Args&gt;(args)...);
}


Brush up on r-value references: Note that your implementation of print(std::string&amp;&amp; fmt, ...) was incorrect. In particular, one of the two uses of std::forward is misleading. Per Scott Meyers, it's useful to think of std::forward as a &ldquo;conditional&rdquo; std::move. It's useful in conjunction with the deduced T&amp;&amp; parameters, which he calls &ldquo;universal references.&rdquo; You use this correctly in the code phrase std::forward&lt;Args&gt;(args)..., which moves each movable sub-args, determined by the type of the corresponding Args, as deduced from the call site.

However the similar looking phrase std::forward&lt;std::string&gt;(fmt) is not conditional at all. It will never move fmt, as it is always casting fmt to an lvalue type. If you were to implement this function (which I recommended against in the previous point), you would want std::forward&lt;std::string&amp;&amp;&gt;(fmt) which is really just a long and hard to read way of writing std::move(fmt). This is correct because print's fmt is an l-value that was moved in, and now you want to move it onward.

The upshot This was a good exercise that ended up focusing a lot on std::move, std::forward, and r-value references in general. There don't appear to have been any functionality problems in your implementation, although it's always good to make sure with more than one test case. If you expand on this function's robustness and capability set (say fixing the limitations from the replace_all approach, or adding a way to escape braced indices so that they aren't substituted) that could make another interesting review. Thanks!