LoginSignup
8
4

More than 5 years have passed since last update.

C++17 でキーワード引数を実装してみた

Last updated at Posted at 2018-12-21

はじめに

C++17 でキーワード引数を実装してみたときの記録です。
GitHub リポジトリは以下にあります。
https://github.com/iorate/cpp-flexargs

使い方

greet.cpp
#include <iostream>
#include <utility>
#include "flexargs.hpp"
using namespace flexargs;

// 次のような関数を、キーワード引数で呼び出せるようにします。
// auto greet = [](auto &&name, auto &&out = std::cout) {
//     out << "Hello, " << name << "!\n";
// };

// キーワードを定義します。
namespace keywords {
    inline constexpr keyword<struct name_> name;
    inline constexpr keyword<struct out_> out;
}

// 可変長テンプレートで実引数を受け取ります。
template <class ...Args>
void greet(Args &&...args) {
    // match() に仮引数の宣言と実引数を渡し、その戻り値で仮引数を初期化します。
    auto [name, out] = match(
        parameter(keywords::name),                // 仮引数を宣言します。
        parameter(keywords::out) = std::cout,     // 仮引数は既定値を持つことができます。
        std::forward<Args>(args)...
    );

    // 関数の本体は普通に書きます。
    out << "Hello, " << name << "!\n";
}

int main() {
    using namespace keywords;

    // 引数 name をキーワードで指定します。引数 out は既定値を持つため省略できます。
    greet(name = "World");

    // 引数の順番を変えることができます。
    greet(out = std::cout, name = "World");

    // 従来通り、引数を順番で指定することもできます。
    greet("Error", std::cerr);
}

動機

久しぶりに C++ を触ったら、C++17 というナウい言語になっていたので、簡単なライブラリを書いてみようと思いました。

設計

関数を呼び出す側が、上記の例のように "自然" な記法 (主観が入りますが) で関数を呼び出せるようにしました。ユーザー定義リテラルによる手法は、キーワードの事前定義が不要になるメリットがありますが、greet("name"_arg = "World") のような不自然な書き方になるので、今回は使っていません。

また、関数を実装する側が、関数の本体を "普通" に仮引数の名前を使って書けるようにしました。BOOST_PARAMETER_FUNCTION() のように。ただし今回は、マクロを使わないという縛りをかけています。

最後に、関数を呼び出す側、実装する側、いずれのミスに対しても、できるだけ簡潔なコンパイルエラーが表示されるようにしました。C++ でテンプレートライブラリを使用したときのエラーメッセージはしばしば膨大かつ分かりにくいですが、その理由の一部には、エラーがライブラリの深部で発生すること、その内容からユーザーが何をミスしたか察しにくいことが挙げられます。今回は、できるだけユーザーのコードでコンパイルエラーが発生するようにし、その内容に分かりやすいメッセージを埋め込むようにしました。

実装

match() 関数は、仮引数と実引数を突き合わせて、仮引数の初期値のタプルを返すように実装されています。これは素直に頑張ればできます。

エラーメッセージを簡潔にするため、ユーザーのミスに対して match() 内でコンパイルエラーを発生させないように努め、代わりにエラーを表す型を返すようにしています。雰囲気としては次のようなエラー処理をしています。

at.cpp
#include <cstddef>
#include <type_traits>

struct index_error {};

template <std::size_t N>
inline constexpr std::integral_constant<std::size_t, N> size_c;

// 配列の要素にアクセスする関数
template <class T, std::size_t N, std::size_t I>
constexpr decltype(auto) at(T (&arr)[N], std::integral_constant<std::size_t, I>) {
    if constexpr (I >= N) {
        return index_error();
    } else {
        return arr[I];
    }
}

int main() {
    int arr[] = {1, 2, 3};

    // 存在しない 4 番目の要素にアクセスしようとしている
    int fourth = at(arr, size_c<3>);
}
$ g++ -std=c++17 at.cpp
at.cpp: In function 'int main()':
at.cpp:22:35: error: cannot convert 'index_error' to 'int' in initialization
     int fourth = at(arr, size_c<3>);
                                   ^
# 22 行が原因で index error が起きたことが分かる

戻り値型 decltype(auto)if constexpr を組み合わせて、正常時は通常の戻り値型が、エラー時はエラーを表す型が返るようにしています。 エラー時は、ユーザーが戻り値を使おうとした場所で、コンパイルエラーが発生します。

実際のエラーの例としては次のようになります。

error.cpp
#include <iostream>
#include <utility>
#include "flexargs.hpp"
using namespace flexargs;

namespace keywords {
    inline constexpr keyword<struct name_> name;
    inline constexpr keyword<struct out_> out;
}

template <class ...Args>
void greet(Args &&...args) {
    auto [name, out] = match(
        parameter(keywords::name),
        parameter(keywords::out) = std::cout,
        std::forward<Args>(args)...
    );

    out << "Hello, " << name << "!\n";
}

int main() {
    using namespace keywords;

    // 引数 name を 2 回指定しようとしている
    greet("World", name = "World");
}
$ g++ -std=c++17 error.cpp
error.cpp: In instantiation of 'void greet(Args&& ...) [with Args = {const char (&)[6], flexargs::detail::keyword_argument<keywords::name_, const char (&)[6]>}]':
error.cpp:26:34:   required from here
error.cpp:13:10: error: cannot decompose class type 'flexargs::detail::syntax_error<flexargs::detail::duplicate_argument<keywords::name_> >' without non-static data members
     auto [name, out] = match(
          ^~~~~~~~~~~
# 短いエラーメッセージの中に、syntax_error<duplicate_argument<name_>> という型が埋め込まれている

まとめ

C++17 でキーワード引数を実装してみました。
C++17 はいいぞ。

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4