convert() utility function

ISO/IEC JTC1 SC22 WG21 N3521

Date:

Jeffrey Yasskin <jyasskin@google.com>

convert() utility function

Skip table of contents

Overview

A few recent threads have expected either return ptr; (Bjarne Stroustrup in c++std-ext-14038) or foo({ptr}) (https://groups.google.com/a/isocpp.org/d/topic/std-proposals/YBnWHvd_dIY/discussion) to invoke the explicit unique_ptr(T*) constructor. Both threads eventually proposed a list of language changes to make the necessary conversion more concise:

In c++std-ext-14049 and independently in https://groups.google.com/a/isocpp.org/d/msg/std-proposals/YBnWHvd_dIY/2iBLM-fAqngJ, I and Vicente J. Botet Escriba proposed a library solution to handle this problem with a single extra token:

template<typename T>
struct Converter {
  T source_;
  Converter(T source) : source_(std::forward<T>(source)) {}
  template<typename U, decltype(static_cast<U>(std::forward<T>(source_)), 1)=0>
  /*implicit*/ operator U() {
    return static_cast<U>(std::forward<T>(source_));
  }
};
template<typename T>
Converter<T> convert(T&& source) {
  return Converter<T>(std::forward<T>(source));
}

Where an explicit conversion is desired, the user needs to write convert(ptr) in a context that expects a particular type.

It's worth calling out some features of this implementation:

Examples

The example from c++std-ext-14038 is easily handled:

unique_ptr<Shape> read_shape(int i)
{
  switch(i) {
  case 1: return unique_ptr<Shape>(new Circle);
  case 2: return convert(new Triangle);
  }
}

The proposed std::split() function yields another use case. We'd like the returned range to be less magic, so it's likely to be a simple range of std::string_ref, which is only explicitly convertible to std::string. If users want to initialize a vector<string> from a split() call, and we adopt a range library like Boost.Range, the constructors from N3456, and generic lambdas, they'd write something like:

std::vector<std::string> v =
    std::split(input) | transformed([](auto s) std::string(s));

This isn't too bad, but convert() helps us shorten it for everything after the first use:

auto converted = transformed([](auto v) std::convert(v));  // Define once, globally.

std::vector<std::string> v = std::split(input) | converted;

Wording

Wording is relative to N3485.

In the "Header <utility> synopsis" in [utility], add:

// [utility.convert] convert:
template <class T> see below convert(T&& source);

Add a sub-section under [utility] named "convert [utility.convert]"

template <class T> see below convert(T&& source);

Requires: If T&& is an rvalue reference type, T shall be MoveConstructible ([moveconstructible]).

Returns: An object with a member of type T that is constructed from forward<T>(source). For exposition only, this member is named source_. [ Note: This member has reference type if source was passed an lvalue. It only implies a move if source was passed an rvalue. -- end note ]

Remarks: