Document number: P0758R1
Date: 2018-06-06
Audience: Library Working Group
Author: Daniel Krügler
Reply-to: Daniel Krügler

Implicit conversion traits and utility functions

Introduction

This proposal became a rather minimalistic proposal by suggesting only a single new trait is_nothrow_convertible, based on the feedback the author got from both the LEWG and LWG discussions.

By means of these suggested new facilities, this paper attempts to resolve the existing library issue LWG 2040 and provides a tool that would allow to resolve LWG 2999.

Revision History

Changes since P0758R0:

Discussion

This paper revision does not repeat the sections Discussion, 2. is_trivially_convertible, 3. decay_copy, and 4. implicit_cast of its predecessor P0758R0.

Please refer to the previous paper regarding general background and (extended) rationale.

Proposal Candidate

is_nothrow_convertible

The is_nothrow_convertible trait

template <class From, class To> 
struct is_nothrow_convertible;

is presumably one of the most often asked for traits related to is_convertible. One of the earliest official notes of the lack of that feature occurred during the publication of the N3255 proposal that shortly before the C++11 finalization attempted to standardize a new Standard Library function template decay_copy as replacement for the existing DECAY_COPY pseudo-function. Among other reasons (especially the lateness of the feature request), this proposal failed, because it couldn't provide the correct conditional exception specification, concluding:

"What we would need is std::is_nothrow_convertible."

Shortly after C++11 standardization, LWG 2040 was filed requesting the addition of is_nothrow_convertible and is_trivially_convertible, but is since then in a kind of zombie state.

There are several concrete use-cases for the is_nothrow_convertible trait:

  1. It could be used to specify the correct noexcept expression for a decay_copy replacement template for DECAY_COPY, which would resolve one important aspect of LWG 2999.

  2. It could be used to restore the valueable (now conditionally) noexcept specification for several non-throwing basic_string functions that had been "string_view"-ified, as described by LWG 2946. As example consider adjusting the currently suggested wording changes for the following find signature:

    template <class T>
    size_type find(basic_string_view<charT, traits> svconst T& t, size_type pos = 0) const noexcept(see below);
    

    -1- Effects: Creates a variable, sv, as if by basic_string_view<charT, traits> sv = t; and then dDetermines the lowest position xpos, if possible, such that both of the following conditions hold: […]

    -2- Returns: xpos if the function can determine such a value for xpos. Otherwise, returns npos.

    -?- Remarks: This function shall not participate in overload resolution unless is_convertible_v<const T&, basic_string_view<charT, traits>> is true and is_convertible_v<const T&, const charT*> is false. The expression inside noexcept is equivalent to:

    is_nothrow_convertible_v<const T&, basic_string_view<charT, traits>>
    
  3. It could be used to define exception-correct — either by user-code or by the Standard Library — a fundamental implicit_cast utility function, as suggested by.

Resolved Issues

If the proposed resolution will be accepted, the following library issues will be resolved:

Number Description
2040 Missing type traits related to is_convertible

Proposed resolution

The proposed wording changes refer to N4750.

  1. Change 23.15.2 [meta.type.synop], header <type_traits> synopsis, as indicated:

    namespace std {
      […]
      // 23.15.6 [meta.rel], type relations
      template <class T, class U> struct is_same;
      template <class Base, class Derived> struct is_base_of;
      template <class From, class To> struct is_convertible;
      template <class From, class To> struct is_nothrow_convertible;
      […]
      
      // 23.15.6 [meta.rel], type relations
      template <class T, class UGt; inline constexpr bool is_same_v
        = is_same<T, U>::value;
      template <class Base, class Derived> inline constexpr bool is_base_of_v
        = is_base_of<Base, Derived>::value;
      template <class From, class To> inline constexpr bool is_convertible_v
        = is_convertible<From, To>::value;
      template <class From, class To> inline constexpr bool is_nothrow_convertible_v
        = is_nothrow_convertible<From, To>::value;
      […]
    }
    
  2. Change 23.15.6 [meta.rel], Table 44 — "Type relationship predicates", as indicated:

    Table 44 — Type relationship predicates
    Template Condition Comments
    template <class From, class To>
    struct is_convertible;
    see below From and To shall be complete
    types, arrays of unknown
    bound, or cv void types.
    template <class From, class To>
    struct is_nothrow_convertible;
    is_convertible_v<From, To>
    is true and the
    conversion, as defined by
    is_convertible, is known
    not to throw any
    exceptions ([expr.unary.noexcept]).
    From and To shall be complete
    types, arrays of unknown
    bound, or cv void types.

Bibliography

N3255 Lawrence Crowl, Daniel Krügler: "C++ Decay Copy"

P0705R0 Tony Van Eerd: "Implicit and Explicit Conversions"

Sample Implementation

Example implementation for the is_nothrow_convertible type trait.

#include <type_traits> // std::enable_if, ...
#include <utility>     // std::forward, std::declval

namespace xstd {

namespace details {

template <class From, class To, bool =
  std::disjunction<
    std::is_void<From>, std::is_function<To>, std::is_array<To>
  >::value
>
struct do_is_nothrow_convertible
{
  using type = std::is_void<To>;
};

struct do_is_nothrow_convertible_impl
{
  template <class To>
  static void test_aux(To) noexcept;

  template <class From, class To>
  static std::bool_constant<noexcept(test_aux<To>(std::declval<From>()))>
  test(int);

  template <class, class>
  static std::false_type
  test(...);
};

template <class From, class To>
struct do_is_nothrow_convertible<From, To, false>
{
  using type = decltype(do_is_nothrow_convertible_impl::test<From, To>(0));
};

} // details

template <class From, class To>
struct is_nothrow_convertible :
  details::do_is_nothrow_convertible<From, To>::type
{ };

template <class From, class To>
inline constexpr bool is_nothrow_convertible_v 
  = is_nothrow_convertible<From, To>::value;

} // xstd