読者です 読者をやめる 読者になる 読者になる

C++17 で名前付き引数を書いてみた 〜完全版〜

C++17 で実装されているらしい、user defined literals の機能を使って、C++17 で名前付き引数を書いてみた。
ちなみに不完全版はこっち。

不完全版は戻り値型を定義する必要があったんですが、完全版では型を定義しなくてもよくしました。
あとデフォルト値も渡せるようにしてみた。

[使い方]

#include <iostream>

// 可変長テンプレート引数で引数を受け取る
template<typename... Args>
auto
disp(Args... args){
    // get({名前}, args...) を使って値を受け取る
    std::cout << "x=" << ::get("x"_, args...) << std::endl;
    std::cout << "y=" << ::get("y"_, args...) << std::endl;
    // default_({デフォルト値}) でデフォルト値を渡す
    std::cout << "z=" << ::get("z"_, args..., default_(0.0)) << std::endl;
}


int
main(){
    // 名前をつけて引数を渡す
    // 名前は文字列 + UDL で定義する
    disp("x"_ = 1, "y"_ = 2);
    disp("y"_ = 1, "z"_ = 2, "x"_ = 3);
    // すべてのデフォルト値を渡したり
    disp("z"_ = 1, default_(42));
}

[出力]

x=1
y=2
z=0
x=3
y=1
z=2
x=42
y=42
z=1

UDL を利用して、文字列を使って名前を定義しています。
C++17 から UDL で文字列が template<typename Char, Char... CS> で受け取ることができるようになったみたいなのでこんな事ができるようになりました。
C++17 以前で名前付き引数を実装するとなると、どうしても引数毎に型を定義する必要があったんですが、この機能が実装されたことにより UDL をつかってもっと簡単に書くことができるようになりました。
C++17 すごい。

[実装]

#include <tuple>

template<typename Char, Char... CS>
struct basic_string{
    
};

template<char... CS>
struct string : basic_string<char, CS...>{};

template<typename Name, typename T>
struct parameter{
    T value;
};

template<typename Name>
struct name{
    template<typename T>
    auto
    operator =(T t){
        return parameter<name, T>{t};
    }
};

template<typename Char, Char... CS>
auto
operator "" _(){
    return name<basic_string<Char, CS...>>{};
}


template<typename T>
struct default_t{
    T value;
};

template<typename T>
auto
default_(T t){
    return default_t<T>{t};
}


template<typename Name, typename... Args>
struct find_name_type;

template<typename Name>
struct find_name_type<Name>{};

template<typename Name, typename T, typename... Args>
struct find_name_type<Name, parameter<Name, T>, Args...>{
    using type = parameter<Name, T>;
};

template<typename Name, typename T, typename... Args>
struct find_name_type<Name, default_t<T>, Args...>{
    using type = default_t<T>;
};


template<typename Name, typename T, typename... Args>
struct find_name_type<Name, T, Args...> : find_name_type<Name, Args...>{};
;



template<
    typename Name,
    typename... Args,
    typename Result = typename find_name_type<Name, Args...>::type
>
auto
get(Name, Args... args){
    return std::get<Result>(std::make_tuple(args...)).value;
}



#include <iostream>


template<typename... Args>
auto
disp(Args... args){
    std::cout << "x=" << ::get("x"_, args...) << std::endl;
    std::cout << "y=" << ::get("y"_, args...) << std::endl;
    std::cout << "z=" << ::get("z"_, args..., default_(0.0)) << std::endl;
}


int
main(){
    disp("x"_ = 1, "y"_ = 2);
    disp("y"_ = 1, "z"_ = 2, "x"_ = 3);
    disp("z"_ = 1, default_(42));
}

[おまけ]

ちなみに元ネタというかきっかけ(っていうか UDL の機能が実装されていると知ったのは)はここ
これ自体はかなり前に教えてもらって、名前付き引数のコード自体は全然覚えてなかったのだけれど、いざ自分で書いてみたらほとんど書き方が同じだった。
デフォルト値の設定方法はこっちの方がよさそうだなー。