yohhoyの日記

技術的メモをしていきたい日記

Perfect Initializationイディオム

Tを値保持するクラステンプレートにおいて(pair, tupleなど)、型Tコンストラクタの explicit 性を継承するイディオム。「ある型Uからの暗黙型変換を許可するか否か」という性質を、型Tをラップするクラステンプレートへと引き継ぐ。

2018-06-09追記:C++2a(C++20)標準ライブラリに向けてP0892R1が採択され、条件付き explicit 指定子の追加によってstd::pair, std::tupleクラステンプレート定義が簡素化される。id:yohhoy:20180609参照。

2016-09-04追記:C++1z(C++17)標準ライブラリに向けてN4387が採択され、std::pair, std::tupleクラステンプレートに同テクニックが適用される。これにより、Uniform initializationによる tuple 初期化がサポートされる。

N4064 Improving pair and tuple, revision 2より説明コードを引用。コンストラクA(U&&)にて、条件is_constructible<T,U>かつis_convertible<U,T>によるSFINAEを行う。

#include <type_traits>
#include <utility>

template<class T>
struct A {
  // non-explicitコンストラクタ
  template<class U,
    typename std::enable_if<
      std::is_constructible<T, U>::value &&
      std::is_convertible<U, T>::value
    , bool>::type = false
  >
  A(U&& u) : t(std::forward<U>(u)) {}

  // explicitコンストラクタ
  template<class U,
    typename std::enable_if<
      std::is_constructible<T, U>::value &&
      !std::is_convertible<U, T>::value
    , bool>::type = false
  >
  explicit A(U&& u) : t(std::forward<U>(u)) {}
  
  T t;
};
struct Im{ Im(int){} };
struct Ex{ explicit Ex(int){} };

A<Im> ai1(1); // OK
A<Im> ai2{2}; // OK

A<Im> ai3 = 3;   // OK
A<Im> ai4 = {4}; // OK

A<Ex> ae1(1); // OK
A<Ex> ae2{2}; // OK

A<Ex> ae3 = 3;   // Error
A<Ex> ae4 = {4}; // Error

テンプレート引数Tに指定する型が explicit コンストラクタを持つ場合、A<Ex>クラスにも explicit コンストラクタを持たせる。そうでない場合は、A<Im>クラスでもコンストラクト時の暗黙型変換を許容する(上記例ではU=intからT=Imへの暗黙型変換)。

C++11(N3337) 20.9.4.3/p6, 20.9.6/p4より一部引用。

template <class T, class... Args> struct is_constructible;

6 Given the following function prototype:

template <class T>
  typename add_rvalue_reference<T>::type create();

the predicate condition for a template specialization is_constructible<T, Args...> shall be satisfied if and only if the following variable definition would be well-formed for some invented variable t:

T t(create<Args>()...);

(snip)

template <class From, class To> struct is_convertible;

4 Given the following function prototype:

template <class T>
  typename add_rvalue_reference<T>::type create();

the predicate condition for a template specialization is_convertible<From, To> shall be satisfied if and only if the return expression in the following code would be well-formed, including any implicit conversions to the return type of the function:

To test() {
  return create<From>();
}

(snip)

関連URL