Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does implicit T& constructor of std::reference_wrapper<T> make it dangerous to use?

Tags:

boost::reference_wrapper<T> has an explicit T& constructor, while std::reference_wrapper<T> has an implicit one. So, in the following code:

foo = bar; 

If foo is a boost::reference_wrapper, the code will fail to compile (which is good, since reference_wrapper does not have the same semantics of an actual reference.

If foo is a std::reference_wrapper, the code will "rebind" foo's reference to bar (instead of assigning the value as one might mistakenly expect it to).

This could result in elusive bugs... Consider the following example:

In version 1.0 of some hypothetical library:

void set_max(int& i, int a, int b) {     i = (a > b) ? a : b; } 

In a new version (1.1), set_max is converted to a template to accept integers of any width (or UDT's) without changing the interface:

template<typename T1, typename T2, typename T3> void set_max(T1& i, T2 a, T3 b) {     i = (a > b) ? a : b; } 

Then finally, in some application using the library:

// i is a std::reference_wrapper<int> passed to this template function or class set_max(i, 7, 11); 

In this example, the library changes its implementation of set_max without changing the call interface. This would silently break any code that passes it a std::reference_wrapper as the argument would no longer convert to int& and would instead "rebind" to a dangling reference (a or b).

My question: Why did the standards committee elect to allow implicit conversion (from T& to std::reference_wrapper<T>) instead of following boost and making the T& constructor explicit?


Edit: (in response to the answer offered by Jonathan Wakely)...

The original demo (in the section above) is intentionally concise to show how a subtle library change could result in the use of std::reference_wrapper introducing bugs to an application.

The next demo is provided to show a real-world, legitimate use of reference_wrapper for "passing references through interfaces", in response to Jonathan Wakely's point.

  • From Developer/Vendor A

Something similar to std::bind but pretend it's specialized for some task:

template<typename FuncType, typename ArgType> struct MyDeferredFunctionCall {     MyDeferredFunctionCall(FuncType _f, ArgType _a) : f(_f), a(_a) {}      template<typename T>     void operator()(T t) { f(a, t); }      FuncType f;     ArgType a; }; 
  • From Developer/Vendor B

A RunningMax functor class. Between version 1.0 and 1.1 of this imaginary library, the implementation of RunningMax was changed to be more generic, without changing its call interface. For purposes of this demo, the old implementation is defined in namespace lib_v1, while the new implementation in defined in lib_v2:

namespace lib_v1 {     struct RunningMax {         void operator()(int& curMax, int newVal) {                 if ( newVal > curMax ) { curMax = newVal; }             }     }; } namespace lib_v2 {     struct RunningMax {         template<typename T1, typename T2>         void operator()(T1& curMax, T2 newVal) {                 if ( newVal > curMax ) { curMax = newVal; }             }     }; } 
  • And last but not least, the end-user of all the above code:

Some developer using the code from Vendor/Developer A and B to accomplish some task:

int main() {     int _i = 7;     auto i = std::ref(_i);     auto f = lib_v2::RunningMax{};      using MyDFC = MyDeferredFunctionCall<decltype(f), decltype(i)>;     MyDFC dfc = MyDFC(f, i);     dfc(11);      std::cout << "i=[" << _i << "]" << std::endl; // should be 11 } 


Note the following:

  • The end-user uses std::reference_wrapper the way in which it's intended.

  • Individually, none of the code has bugs or logical flaws, and everything worked perfectly with the original version of Vendor B's library.

  • boost::reference_wrapper would fail to compile upon upgrading the library, while std::reference_wrapper silently introduces a bug that may or may not be caught in regression tests.

  • Tracing such a bug would be difficult, since the "rebinding" is not a memory-error that tools such as valgrind would catch. Furthermore, the actual site of the misuse of std::reference_wrapper would be within Vendor B's library code, not the end-user's.

The bottom line: boost::reference_wrapper seems safer by declaring its T& constructor as explicit, and would prevent the introduction of a bug such as this. The decision to remove explicit constructor restriction in std::reference_wrapper seems like it compromised safety for convenience, something that should rarely occur in language/library design.

like image 639
etherice Avatar asked Mar 26 '13 20:03

etherice


People also ask

Does implicit means true?

Something that's described as explicit doesn't leave anything up to interpretation. In contrast, the adjective implicit describes something that has been implied—meaning it has been suggested or hinted at but not actually directly stated or expressed.

What does it mean if something is implicit?

'Implicit' Meaning Implicit, on the other hand, denotes that something is understood although not clearly or directly expressed or conveyed—there is implication, assumption, or question. It often precedes a preposition, usually in and less frequently from, with, or within.

What is an example of a implicit?

The definition of implicit refers to something that is suggested or implied but not ever clearly said. An example of implicit is when your wife gives you a dirty look when you drop your socks on the floor. Having no doubts or reservations; unquestioning.

Is implicit the opposite of explicit?

Summary. Implicit and explicit have near opposite meanings, so it's important to remember their difference. Implicit is indirectly stated or implied. Explicit is directly stated and spelled out.


2 Answers

This would silently break any code that passes it a std::reference_wrapper as the argument would no longer convert to int& and would instead "rebind" to a dangling reference (a or b).

So don't do that.

reference_wrapper is for passing references through interfaces that would otherwise make a by-value copy, it's not for passing to arbitrary code.

Also:

// i is a std::reference_wrapper<int> (perhaps b/c std::decay wasn't used)

decay wouldn't change anything, it doesn't affect reference wrappers.

like image 154
Jonathan Wakely Avatar answered Nov 18 '22 03:11

Jonathan Wakely


The reason implicit conversion (T& --> reference_wrapper<T>) is allowed for std::reference_wrapper<T>, but not boost::reference_wrapper<T>, is sufficiently explained in the DR-689 link provided by Nate Kohl. To summarize:

In 2007, the C++0x/C++11 Library Working Group (LWG) proposed change #DR-689 to section 20.8.3.1 [refwrap.const] of the standard:

The constructor of reference_wrapper is currently explicit. The primary motivation behind this is the safety problem with respect to rvalues, which is addressed by the proposed resolution of [DR-688]. Therefore we should consider relaxing the requirements on the constructor since requests for the implicit conversion keep resurfacing.

Proposed resolution: Remove explicit from the constructor of reference_wrapper.

It's worth pointing out:

  • boost::reference_wrapper has not been relaxed in such a way, nor does there appear to be a proposal for it, which creates an inconsistency between the semantics of boost::reference_wrapper and std::reference_wrapper.

  • Based on the verbiage in DR-689 (specifically the "requests keep surfacing" part) it seems likely that this change was simply viewed by the LWG as an acceptable tradeoff between safety and convenience (in contrast to its boost counterpart).

  • It's unclear whether the LWG anticipated other potential risks (such as those demonstrated in the examples provided on this page), since the only risk mentioned in DR-689 was that of binding to an rvalue (as described and resolved in the previous entry, DR-688).

like image 25
etherice Avatar answered Nov 18 '22 02:11

etherice