0

この質問とこの他の質問を組み合わせて、次の(実際には非常に単純な)解決策にたどり着きました。アイデアは、実際の関数の範囲内でのみ型エイリアスを使用可能にし、適切なポイントでテンプレートの条件を確認することです。

template<typename... garbage>
struct firewall
{
   typedef typename std::enable_if<sizeof...(garbage) == 0>::type type;
};

#define FIREWALL_CALL typename = typename firewall<garbage...>::type
#define TMPL_FIREWALL typename... garbage, FIREWALL_CALL
#define TMPL_ALIAS typename
#define TMPL_CONDITION(...) typename = \
                              typename std::enable_if<__VA_ARGS__::value>::type

このコードを使用すると、快適な方法で必要なエイリアスまたは条件を追加できます。このコード:

// Erase only qualifiers and references.
template<typename target>
struct pluck
{
   typedef typename std::remove_cv<
       typename std::remove_reference<target>::type>::type type;
}

// `some_fun` wants ensure its arguments are passed by non-constant values.
template<typename T>
typename pluck<T>::type
some_fun(typename pluck<T>::type a, typename pluck<T>::type b)
{
   typename pluck<T>::type c;

   // something

   return c;
}

になります(のみsome_fun):

template<typename T, TMPL_FIREWALL,
         TMPL_ALIAS friendly = typename pluck<T>::type
         TMPL_CONDITION(std::is_copy_constructible<friendly>)>
friendly some_fun(friendly a, friendly b)
{
   friendly c;

   // something

   return c;
}

上記の 2 番目の質問で@ipcfirewallが示しているように、の目的は、デフォルトのテンプレート引数として定義されたローカル型エイリアスを再配置できる引数を吸収することです。

また、これにより、他のより深い問題を回避するための便利な方法が利用可能になります。事前に既知のタイプの完全な転送を行うためだけに関数をパラメトリックにしたい場合。たとえば、次の状況では:

struct A
{
   template<typename... Args>
   A(Args&&... args) : _b(std::forward<Args>(args)...)
   {}

   template<typename Str>
   A(Str&& str) : _str(std::forward<Str>(str))
   {}

   B _b;
   std::string _str;
};

完全な転送メカニズムを使用して初期化する場合_str、他のテンプレート引数とあいまいになることは避けられません。これは、次の追加マクロを使用して簡単に回避できます。

#define TMPL_PURE_FORWARDING(a, b) TMPL_FIREWALL, \
          TMPL_CONDITION(std::is_same<typename _f_pluck<a>::type, b>)

struct A
{
   template<typename... Args>
   A(Args&&... args) : _b(std::forward<Args>(args)...)
   {}

   template<typename fwStr, TMPL_PURE_FORWARDING(fwStr, std::string)>
   A(fwStr&& str) : _str(std::forward<fwStr>(str))
   {}

   B _b;
   std::string _str;
};

fwStrタイプstd::stringstd::string&、またはそれらの定数バージョンでない場合はstd::string&&、他のコンストラクターが選択され、他のコンストラクターがない場合は、std::enable_if<false, void>::type存在しないというコンパイラ エラーがスローされます。

質問: C++ では常にマクロの使用を避けることが望ましいですが、よく知られているテンプレートは冗長であり、これらの状況 (特に 2 番目の状況) は非常に一般的です。少なくとも私の経験では。次に、これらのマクロは非常に便利です。

この状況でマクロを使用するのは危険ですか? これは一般的に良いものですか、それとも有用ですidiomか、それとも何もないようですか?

4

1 に答える 1

1

私はマクロを使用しません。

マクロが唯一の可能性である状況もありますが、あえてそうではありません。マクロは生のテキスト処理を実行します。それらが処理するものは C++ メタモデルの一部ではなく、無意味なテキストです。それらは安全ではなく、理解しにくく、維持するのが困難です。したがって、他に方法がない場合を除き、マクロは使用しないでください。

さらに、あなたのpluck<>特性は基本的に何をstd::decay<>しますか?これは、単純なテンプレート エイリアスを使用して、読みやすく、some_fun解析しやすいように関数を書き直すことができることを意味します(これらのマクロのすべての部分をまとめようとして迷ってしまいました)。

#include <type_traits>

template<typename T>
using Decay = typename std::decay<T>::type;

template<typename T> 
Decay<T> some_fun(Decay<T> a, Decay<T> b)
{
    Decay<T> c;

   // something

   return c;
}

同様に、2 番目のユース ケースでは、次のように記述できます。

template<typename T, typename U>
using CheckType = typename std::enable_if<
    std::is_same<typename std::decay<T>::type, U>::value
    >::type;

struct A
{
    template<typename... Args>
    A(Args&&... args) : _b(std::forward<Args>(args)...)
    {}

    template<typename T, CheckType<T, std::string>* = nullptr>
    A(T&& str) : _str(std::forward<T>(str))
    {}

    B _b;
    std::string _str;
};
于 2013-02-05T00:03:33.587 に答える