LoginSignup
238
241

More than 5 years have passed since last update.

C++11からC++17を駆け抜けるC++講座

Last updated at Posted at 2017-12-16

初心者C++Advent Calendar 2017

この記事は初心者C++Advent Calendar 2017 15日目の記事です

<< 14日目|C++を学ぶにあたって参考にした書籍・Webサイト || 16日目|何か書きます >>

今週3本目、このAdCでも3本目の記事です。期限に間に合わなかったでござる・・・。

今年の初心者AdC++は平和ですね・・・。

はじめに

みなさま、ナマステ。さて、この記事のタイトルを見て、「時代・・・サトウキビ・・・忍者・・・うぅ、頭が」となった人は私と趣味が似ています・・・って話をするのは何回目だろうか。

先日ニコニコ(デクレッシェンド)が発表されましたが、Qiitaはクレッシェンドしていって貰いたいものです(なおデザイン改悪)。
(追記:と、この記事を書いていたら百花繚乱氏が登場した動画と生放送サービスに対する意見交換会が開かれましたね。われこそはというエンジニアの皆様はぜひドワンゴへ!(謎の宣伝))

話を戻しまして・・・。

統一初期化についての言及がやたら多かった印象のあるC++初心者Advent Calendar 2015ではC99からC++14を駆け抜けるC++講座と題してC++14まで駆け抜けましたが時代はC++17。やっぱり時代の先端まで駆け抜けたいですよね。

なんか結果的に、C++ Advent Calendar 201719日目の記事
1 ページで眺める C++17 - 進捗置き場というわけでもない場所
とネタかぶりした感があります。

(C++11 ... C++14]

変更点一覧は
C++14 - cpprefjp C++日本語リファレンス
にまとまっています。

C++14の変更は項目こそ多くないものの無視できない変更があります。

通常関数の戻り値型推論

C++11で導入されたlambda式は

C++11
auto f = [](int a){ return a + 3; };
static auto n = f(1);//int型

のように戻り値の型を推論できました。しかし通常の関数においては

C++11
auto f(int a) -> decltype(a + 3) { return a + 3; }
static auto n = f(1);//int型

のようにdecltypeを使いreturn文と同じ内容を書く必要があり、極めて冗長でした。

C++14ではこれを推論させることができます。

C++14
auto f(int a){ return a + 3; }
static auto n = f(1);//int型

ジェネリックラムダ

lambda式の引数の型はlambda式の使われ方からして自明なので推論してほしいと思うことが多々あります。とくに型名が長いときとかとか。

そう思った例として幾つか。

C++でマイナンバーのチェックデジットを計算する - Qiitaでは

C++11
#include <string>
#include <utility>
#include <numeric>//std::accumulate()
#include <stdexcept>
#include <cctype>//std::isdigit()
int calc_check_digit(const std::string& num) noexcept(false) {
    if (11 != num.length()) throw std::runtime_error("num.digit must be 11");
    const int remainder = std::accumulate(num.rbegin(), num.rend(), std::pair<int, int>{}, [](const std::pair<int, int>& s, const char& e){
        if(!std::isdigit(e)) throw std::runtime_error("num.digit must be 11");
        const int n = s.second + 1;
        const int p = e - '0';
        const int q = (6 < n) ? n - 5 : n + 1;
        return std::pair<int, int>{s.first + p * q, n};
    }).first % 11;
    return (0 == remainder || 1 == remainder) ? 0 : 11 - remainder;
}

のようなコードを書きましたが、const std::pair<int, int>& sってすごく冗長ですよね。

@YukiMiyatake 氏のBoostAsioで可読性を求めるのは間違っているだろうかでは

slide 19/31

asio::yield_context yield_childってやっぱりすごく冗長ですよね。

ジェネリックラムダはここにautoを指定できるようにするものです。

C++14
auto plus = [](auto a, auto b) { return a + b; };

というジェネリックラムダは

C++14
template <class T1, class T2>
auto operator()(T1 a, T2 b) const
{
  return a + b;
}

という演算子を持つ関数オブジェクトを生成します。テンプレートと同じ型推論なのでcv修飾子(const, volatile)、参照、ポインタといった修飾ができます

C++14
auto plus = [](const auto& a, const auto& b) { return a + b; };

なおもっとも汎用的なauto&&を使う場合

C++14
[]( auto && a, auto && b ) noexcept(a < b) -> decltype(auto){ return a < b ; }

これを

P0573提案
[](a,b) => a < b

のようにかける提案が出ている。ECMAScript2015のArrow functionやC#3.0のlambdaみたいだ。

[初心者向け]変数テンプレート

C++初心者ならテンプレート扱えるよね。

みんなだいすき<type_traits>ヘッダなんかにあるstd::is_integralのような類は使うときに

C++14
//static member変数の参照
std::is_integral<T>::value;
//operator()の呼び出し
std::is_integral<T>();

のように書くことになるが、

C++14
#include <type_traits>
template <class T>
constexpr bool is_integral_v = std::is_integral<int>::value;

のように変数テンプレートを定義しておくことで

C++14
is_integral_v<T>

とかけるようになる。

こんな感じに実質type_traitsのようなものを書くときにしか使わない印象。

なおC++17で自分で変数テンプレート書かなくても

C++17
std::is_integral_v<T>

とかけるようになりました。

constexprの制限緩和

C++14最大の変更点と言って過言ではないです。

C++11でconstexprが導入され、コンパイル時計算が一般的な人類の手の届くものになりました。

slide12/100

中3女子でもわかる constexpr from Genya Murakami

そう、そのはずでした。しかしC++11のconstexprは制約が多く

  • return文一つしか書けないので三項演算子で頑張る
  • 副作用は作れないので再帰とラッパー関数で頑張る
  • 再帰回数には推奨値512回の制約があるので、再帰オーダーを下げるためにindex_tuple や 倍分再帰 などのイディオムを駆使する必要がある。

という事態を招き、例えば次のようなコードが生産されることになりました。

C++11
/*=============================================================================
  Copyright (c) 2011-2017 Bolero MURAKAMI
  https://github.com/bolero-MURAKAMI/Sprout

  Distributed under the Boost Software License, Version 1.0. (See accompanying
  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
=============================================================================*/
#ifndef SPROUT_ALGORITHM_FIND_HPP
#define SPROUT_ALGORITHM_FIND_HPP

#include <iterator>
#include <type_traits>
#include <sprout/config.hpp>
#include <sprout/iterator/operation.hpp>
#include <sprout/iterator/type_traits/category.hpp>
#include <sprout/utility/pair/pair.hpp>

namespace sprout {
    namespace detail {
        template<typename RandomAccessIterator, typename T>
        inline SPROUT_CONSTEXPR RandomAccessIterator
        find_impl_ra(
            RandomAccessIterator const& first, RandomAccessIterator const& last, T const& value,
            typename std::iterator_traits<RandomAccessIterator>::difference_type pivot, RandomAccessIterator const& found
            )
        {
            return found != first ? found
                : pivot == 0 ? (*first == value ? first : last)
                : sprout::detail::find_impl_ra(
                    sprout::next(first, pivot), last, value,
                    (sprout::distance(first, last) - pivot) / 2,
                    sprout::detail::find_impl_ra(
                        first, sprout::next(first, pivot), value,
                        pivot / 2,
                        first
                        )
                    )
                ;
        }
        template<typename RandomAccessIterator, typename T>
        inline SPROUT_CONSTEXPR typename std::enable_if<
            sprout::is_constant_distance_iterator<RandomAccessIterator>::value,
            RandomAccessIterator
        >::type
        find(
            RandomAccessIterator const& first, RandomAccessIterator const& last, T const& value,
            std::random_access_iterator_tag*
            )
        {
            return first == last ? last
                : sprout::detail::find_impl_ra(first, last, value, sprout::distance(first, last) / 2, first)
                ;
        }

        template<typename InputIterator, typename T>
        inline SPROUT_CONSTEXPR sprout::pair<InputIterator, bool>
        find_impl_1(
            sprout::pair<InputIterator, bool> const& current,
            InputIterator const& last, T const& value, typename std::iterator_traits<InputIterator>::difference_type n
            )
        {
            typedef sprout::pair<InputIterator, bool> type;
            return current.second || current.first == last ? current
                : n == 1 ? *current.first == value ? type(current.first, true) : type(sprout::next(current.first), false)
                : sprout::detail::find_impl_1(
                    sprout::detail::find_impl_1(
                        current,
                        last, value, n / 2
                        ),
                    last, value, n - n / 2
                    )
                ;
        }
        template<typename InputIterator, typename T>
        inline SPROUT_CONSTEXPR sprout::pair<InputIterator, bool>
        find_impl(
            sprout::pair<InputIterator, bool> const& current,
            InputIterator const& last, T const& value, typename std::iterator_traits<InputIterator>::difference_type n
            )
        {
            return current.second || current.first == last ? current
                : sprout::detail::find_impl(
                    sprout::detail::find_impl_1(
                        current,
                        last, value, n
                        ),
                    last, value, n * 2
                    )
                ;
        }
        template<typename InputIterator, typename T>
        inline SPROUT_CONSTEXPR InputIterator
        find(
            InputIterator const& first, InputIterator const& last, T const& value,
            std::input_iterator_tag*
            )
        {
            typedef sprout::pair<InputIterator, bool> type;
            return sprout::detail::find_impl(type(first, false), last, value, 1).first;
        }
    }   // namespace detail

    // 25.2.5 Find
    //
    //  recursion depth:
    //      O(log N)
    //
    template<typename InputIterator, typename T>
    inline SPROUT_CONSTEXPR InputIterator
    find(InputIterator first, InputIterator last, T const& value) {
        typedef typename std::iterator_traits<InputIterator>::iterator_category* category;
        return sprout::detail::find(first, last, value, category());
    }
}   // namespace sprout

#endif  // #ifndef SPROUT_ALGORITHM_FIND_HPP

ここに崩壊したかに見えたコンパイル時の世界の魔界は完全にその権威を取り戻した(Doctor X風)。

C++14constexprはこのコンパイル時計算を再び一般的な人類の手の届くものにするものです。

このconstexprの制限緩和によって、コンパイル時にできないことは概ね

  • I/O
  • 動的メモリ(new/delete)
  • 例外処理(try-catch)
  • RAII(デストラクタを使った後処理)
  • ラムダ式
  • グローバル変数の参照

になりました。ラムダ式については後述するがC++17で概ね制約がなくなりました。動的メモリについては現在C++標準化委員会にこれを許可する提案が出ています。I/OのうちInputの部分はFile literalなる奇想天外なものが同じくC++標準化委員会に提案されています。

結果として先の魔界じみたコードは、概ね次のような一般的な人類でも読めるものに変貌を遂げたのです。

C++14
//Copyright (c) 2013-2014 Bolero MURAKAMI
template<typename InputIterator, typename T>
constexpr InputIterator
find(InputIterator first, InputIterator last, T const& value) {
  for (; first != last; ++first)
    if (*first == value) {
      return first;
    }
  return last;
}

もはや普段我々が書く関数と差はほとんどないですね!
縄文アートグループ展 ARTs of JOMON in Kioicho(12/4~12/25)に出品している岡山の陶芸家な某中3女子でなくても理解できます。
constexprは市民の義務と言えるでしょう。

宣言時のメンバ初期化を持つ型の集成体初期化を許可

細かいこと抜きにして実用的な話をすると

C++11
std::array<int, 3> ar = {{ 1, 2, 3 }};

C++14
std::array<int, 3> ar = { 1, 2, 3 };

とかけるようになったという話です。

[[deprecated]]属性

本来の用途はライブラリ開発者がユーザーに「このAPIは使わないでくれ、そのうち削除する」と伝えるためのものです。
このattributeが付いているとコンパイラは警告なりエラーを吐きます。

これを悪用して、リファクタリングするときに消したいものにこのattributeをつけて利用されているところを列挙するという使い方があります。

grepすればいいやん、とかVSのIntelliSenseで呼び出しツリーとか参照見れるでしょ、と思うかもしれませんが、grepだと別の名前空間のやつが引っかかったりして面倒で、VSのはわりとよくバグるというかコンパイラとIntelliSenseで別のバグを同時に引くと解読不能なのでこいつがすごく便利です。

以前から各コンパイラが独自拡張でこの機能を実装していたので、それが標準化されたと思っていいと思います。

std::make_unique()を追加

なぜC++11にmake_shardがあるのにmake_uniqueがないのかという思いでいっぱいですが、C++14で追加されました。

C++14
auto p1 = std::make_unique<std::pair<int, int>>(3, 1);

これによってnew/deleteは書いてはいけないものになりました。代わりにstd::unique_ptr/std::shard_ptrを使いましょう。

なおどのようにスマートポインタを使うべきかは
C++ スマートポインタのパターン
というスマートな記事を発見したのでそちらを参照してください。

std::exchange()を追加

これ自体は別に大した関数じゃなくて、値を書き換え、書き換え前の値を返す、というただそれだけの関数です。真の目的は
非推奨だった bool 型に対するインクリメント演算子を削除(C++17)
への布石です。

リテラル演算子

痒いところに手が届く新機能です。

これまで

C++11
#include <iostream>
#include <string>
#define STRINGIFY(n) #n
#define TOSTRING(n) STRINGIFY(n)

std::string foo(const char* str1)
{
    return std::string("s1:") + str1 + " in line" + TOSTRING(__LINE__);
}
int main()
{
    std::cout << foo("arikitari") << std::endl;
}

とか

C++11
#include <chrono>
#include <thread>
int main()
{
    const auto current = std::chrono::high_resolution_clock::now();
    //do something
    std::this_thread::sleep_until(current + std::chrono::seconds(3));
}

みたいなコードを書いていたと思いますが、std::string("s1:")にしてもstd::chrono::seconds(3)にしても冗長で、using namespaceとかusing s = std::stringとかするのもだるいですよね。

ところでC++11で、User Defined Literalsという機能が追加されました。こいつを標準ライブラリで追加したのがC++14での変更です。つまり

C++14
#include <iostream>
#include <string>
#define STRINGIFY(n) #n
#define TOSTRING(n) STRINGIFY(n)

std::string foo(const char* str1)
{
    using namespace std::string_literals;
    //using namespace std::literals;//これでもいい
    //using namespace std::literals::string_literals;//これでもいいが長い
    //using namespace std;//しないことを強くすすめる
    return "s1:"s + str1 + " in line" + TOSTRING(__LINE__);
}
int main()
{
    std::cout << foo("arikitari") << std::endl;
}

とか

C++14
#include <chrono>
#include <thread>
int main()
{
    using namespace std::chrono_literals;
    //using namespace std::literals;//これでもいい
    //using namespace std::literals::chrono_literals;//これでもいいが長い
    //using namespace std;//しないことを強くすすめる
    //using namespace std::chorono;//しないことをすすめる
    const auto current = std::chrono::high_resolution_clock::now();
    //do something
    std::this_thread::sleep_until(current + 3s);
}

のようにかけるわけです。すっきり。

例は挙げていませんが複素数ライブラリ(<complex>)向けにも追加されています。

(C++14 ... C++17]

変更点一覧は
C++17 - cpprefjp C++日本語リファレンス
にまとまっています。

cpprefjpではC++17の新機能解説を現在執筆中ですが、それに加わる人をきっと、多分、おそらく、may be、募集していると思います。
Issues · cpprefjp/site | is:issue is:open label:TASK C++17
この辺を見て書けそうなのがあったらPull Requestを投げてください。
(早くif constexpr文の導入背景を調べて追記しないと・・・)

あと @kazatsuyu 氏の
久々なのでC++17の情報を集めてみる - Qiita
と内容かぶっているじゃねーか、という話もありますが、もうすこしざっくりした話をします。

インライン変数

C++ではtemplateを使うと実質その定義をヘッダに書かないといけないので、開き直ってヘッダーオンリーライブラリというのが広く存在します(コンパイル時間は犠牲になった)。

しかしながら、例えばmutexなど外部リンケージを持つ変数を作る必要があることがあり、ヘッダーオンリーでは素直には記述できませんでした。

インライン変数はこれを解決します。

C++14ヘッダー
struct X {
  // ヘッダでは変数の宣言のみを行い
  static int foo;
};
C++14ソース
// 変数fooを定義する
int X::foo;

これが

C++17
struct X {
  // ソースで変数fooを定義する必要がない
  static inline int foo;
};

のようになります。

恩恵としてはヘッダーオンリーのライブラリが増えて気軽に外部ライブラリを使えるようになるんじゃないかと思います。(そんなことよりはようmoduleよこせ)

構造化束縛

個人的にイチオシのC++17新機能です。

C++11
std::pair<int, std::string> f() { return { 2, "s" }; }
int main()
{
    std::pair<int, std::string> p = f();
    int id = p.first;
    std::string message = p.second;
}

のように書いていたのが

C++17
std::pair<int, std::string> f() { return { 2, "s" }; }
int main()
{
    auto [id, message] = f();
}

と書けるようになる機能です。

実際の利用例としては、

C++17
//Copyright Cpprefjp CC BY
#include <iostream>
#include <map>
#include <string>

int main()
{
    std::map<std::string, int> m = {
        {"Alice", 3},
        {"Bob", 1},
        {"Carol", 4}
    };

    // mapの各要素をキーと値に分解する。
    // const auto&ではなくauto&にした場合は、
    // const std::string&型のkey変数と、
    // int&型のvalue変数に分解される。
    for (const auto& [key, value] : m) {
        std::cout << key << " : " << value << std::endl;
    }
}

のように使えます。

で、こいつのポテンシャル(潜在的能力)が意外に高くて、

C++17
#include "print_struct.hpp"
#include <iostream>
struct inner
{
  const char *p;
  int n;
};
struct outer
{
  int m;
  inner i;
};
int main()
{
  outer o{3, {"foo", 88}};
  using namespace print_struct;
  std::cout << o << '\n';
}

魔法のヘッダーファイルprint_struct.hppをincludeし、using namespace print_struct;operator<<を名前探索の対象に含めることで、publicなメンバ変数を含むクラスがstd::coutで表示できるということができたりします。
Playful Programming: Serializing structs with C++17 structured bindings

[[maybe_unused]]属性

デバッグ用にassertの呼び出しを書くときに一回変数に受けておきたいということがあるわけですが、そうするとReleaseビルドするときに、「変数~~は利用されていません」みたいに怒られると悲しいですね。なので仕方なく#ifdef _DENUG〜〜#endifとかする。でもそれって非常に非効率的。これはattributeの出番でしょ!ということで追加されました。

値のコピー省略を保証

これまでC++の多くのコンパイラはRVO(Return Value Optimization)と呼ばれる最適化を実装してきました。つまり、

//copy
#include <iostream>
struct Hoge {
  Hoge(int i = 0) : x(i) {}
  Hoge(const Hoge&) { std::cout << "copy ctor call"; }
  int x;
};
Hoge rvo() {
  return Hoge();
}
int main() {
  Hoge a = rvo();  // copy が呼ばれないかもしれない(RVO)
  Hoge b = a;  // copy が呼ばれる
  return 0;
}

このようにコピー/ムーブコンストラクタの呼び出しを省略することがありました。

C++17ではこのRVOを事実上強制し、コピー/ムーブコンストラクタがなくても関数の戻り値として使えるようになりました。

なおこれよりさらに進んだNRVOに関しては変化なしなのでその場合はコピー/ムーブコンストラクタが必要です。

if文とswitch文の条件式と初期化を分離

変数のスコープをなるべく小さくしろというのはよく言われる話で、言語側でも

C99_C++11
for(int i = 0; i < 55; ++i){
    //do something
}

というようにfor文のなかで変数宣言ができるようになったりしていました。

C++17ではif文とswitch文でもそれができるようになりました。

C++17
std::string s;
//do something
if(std::size_t pos = s.find_first_of(' '); std::string::npos != pos){
   //do something
}

if constexpr

これまでC++には2つのif文がありました

  • プリプロセス時: #if#elif#else#endif
  • 実行時: if

ここに新たに

  • コンパイル時: if constexpr

が追加されます。

もはや
C99からC++14を駆け抜けるC++講座 - Qiita#templateでif
で取り上げたクラステンプレートの部分特殊化を利用したコンパイル時条件分岐やSFINAEを利用したコンパイル時条件分岐やオーバーロードを利用したコンパイル時条件分岐(可変長引数などで)は忘れていいものになりました。

constexprラムダ

C++11で追加されたlambda式が関数オブジェクトを生成するsyntax sugerであるというのは
関数の創世から深淵まで駆け抜ける関数とはなんぞや講座 - Qiita
で触れましたが、その生成される関数オブジェクトのoperator ()が、条件を満たすと自動的にconstexpr指定されます、という機能です。

C++17
int main()
{
    auto f = []{ return 42 ; } ;

    constexpr int value = f() ; // OK
}

なお一部のC++erの皆様に申し上げると、SFINAEの文脈では使えません。C++20はよ!

畳み込み式(fold式)

これは無視できない新機能ではあるんだけど、魔界過ぎて解説は省略。C++14のconstexpr制限緩和のところで書いた再帰オーダーを下げるというのに大きく貢献するので、岡山の陶芸家な某中3女子あたりが喜びそうではあります。
がしかし再帰地獄も大概だったけれど、fold式も大概だと私は思うんだ。

まあなんだ、

C++17
template<typename ...Args>
auto sum(Args&& ...args) {
    return (... + args);
}

みたいな()で囲まれて...が出てきて、C++11の可変長引数の展開ではなさそうだったらこれだ。

ただ書けなくても読めないとC++17時代こいつを見かける機会は増えるかもしれない。

それは2017年8月のこと。

C++17
#include <iostream>
#include <cstddef>
#include <algorithm>

template<typename ...Types>
constexpr auto max_sizeof_v = []() constexpr {
    std::size_t v = 0;
    return ((v = std::max(sizeof(Types), v)), ..., v);
}();

int main()
{
    std::cout << max_sizeof_v<std::int64_t, int, char, bool> << std::endl;
}

こういうコードにある日突然遭遇するかもしれないから。

なおこの一連のツイートにはオチがあってC++14でもっと短くかけることが判明しているので興味がある人は辿られたし

filesystem

みなさん、聞きましたか?この現代社会、ファイル/ディレクトリの操作すら標準APIを提供できないクソ言語がここにあったようです!

やっとかよ、という思いが満載です。ようやくパーミッションとかとか含めたファイル/ディレクトリの操作ができるようになります!

解説するとそれだけで記事が一つ書けるので、江添さんが執筆している

cpp17book/073-cpp17-lib-filesystem.md at master · EzoeRyou/cpp17book

に解説は譲ります。

Parallelism

C++11で<thread>が入り、C++でもようやくスレッドを作れるようになったわけですが、これを効率的に使うのは意外と難しかったりします。

例えばもっとも要望が多そうなfor文をマルチスレッド化したい、という場合(ループ間に依存がない場合)

C++11
namespace parallel {
    template<
        typename Index, typename Func,
        typename RawIndexType = Index,
        typename ...Args,
        std::enable_if_t<std::is_unsigned<Index>::value && std::is_integral<RawIndexType>::value, std::nullptr_t> = nullptr
    >
    inline void par_for(Index num, Func&& f, Args&& ...args) {
        const unsigned int thread_num = std::thread::hardware_concurrency();
        if (thread_num < 2) {//thread 非対応
            f(static_cast<RawIndexType>(0), static_cast<RawIndexType>(num), std::forward<Args>(args)...);
        }
        else {
            const auto task_num = num / thread_num;
            const auto task_rest = num % thread_num;
            std::vector<std::thread> th;
            th.reserve(thread_num);
            for (unsigned int i = 0; i < thread_num; ++i) {
                th.emplace_back(
                    std::forward<Func>(f),
                    static_cast<RawIndexType>((i) ? i * task_num + task_rest : 0),
                    static_cast<RawIndexType>((i + 1) * task_num + task_rest),
                    std::forward<Args>(args)...
                );
            }
            for (auto&& t : th) t.join();
        }
    }
}
namespace sigmoid_contrast {
    BOOL proc(FILTER* fp, FILTER_PROC_INFO* fpip) // This is the main image manipulation function
    {
        const filter_proxy fc(fp);
#ifdef USECLOCK
        ch::time_point<ch::steady_clock> start_con;
        if (fc.any_of(check::echo_benchmark, check::save_benchmark)) start_con = ch::steady_clock::now();
#endif

        ST.change_param(fc[track::midtone] / 100.0f, static_cast<float>(fc[track::strength]));

        if (fc.none_of(check::R, check::G, check::B)){
            /* Scan Y channel data */
            parallel::par_for(fpip->h, [fpip](int begin, int end) {
                for (int r = begin; r < end; r++){
                    for (int c = 0; c < fpip->w; c++){
                        PIXEL_YC* const px = fpip->ycp_edit + r* fpip->max_w + c;
                        px->y = ST.lookup(px->y);
                    }
                }
            });
        }
    }
}

(コードはyumetodo/SigContrastFastAviUtl: Sigmodial/Logit contrast Aviutl plugin. IM is not used.より抜粋)

のように書けばいいように見えます(すでに十分面倒)。

ところがこれだとスレッドを並列化したいforごとに作る事になります。一般にスレッドを作るコストはそこそこ重いので、高速化のためには一度作ったスレッドを使いまわすようにする必要があります。

そうなると生産者-消費者パターンを実装して・・・となり、おおよそ普通にプログラマが書けるコードではなくなります。

そこでこれまではIntel© TBBなんかを使ってきたのですが(上記コードをそれで書き換えると約5倍高速化)、C++標準でそれはやってほしい。

そういう要望に答えるべくC++17では、<algorithm>ヘッダ全般およびその他一部関数で並列化ができるようなオーバーロードを追加しました。

実行ポリシー(std::execution::seq, std::execution::par, std::execution::par_unseq)を定義して、それを引数に渡してあげるとそれに基づいて実行してくれます。

詳細な解説は使いたいアルゴリズム関数のページ、もしくは

cpp17book/044-cpp17-lib-parallel-algorithm.md at master · EzoeRyou/cpp17book

を参照してください。

なお今回のは第一弾にすぎないので第二弾があるっぽいです。

std::optional

Rustなどと違い、C++のstd::optionalはイテレータを持たないので、

#include <iostream>
std::optional<int> foo() { return 3; }
int main()
{
    for(auto&& i : foo()){
        //only when optional holds value
        std::cout << i << std::endl;
    }
}

のようなことはできません。

要望はあるみたいだし、Sproutはそれを実装しているんですけどね~。

<iterator>size()追加

これで_countofとかnumofとかNUMOFとかその手のマクロを作らなくても配列の大きさが求められます!

[中級者向け]多相アロケータとメモリプール

C++中級者ならメモリーアロケータの存在くらいは知っているはず。C++17ではこれまでのアロケーター利用の仕様への謝罪と反省の意を込めて(?)std::pmr名前空間が追加され、std::prm::memory_resourceを継承したアロケータを書けばいい感じになるようになりました。

・・・などと知ったかぶって書こうとしましたが、私はC++中級者ではないので理解できていません。

@MitsutakaTakeda 氏のAltPlus Advent Calendar 2017 17日目の記事である
Polymorphic Allocator in C++17 - Qiita

に丸投げします。

標準イテレータ全般とarrayの変更操作にconstexprを追加

最適化の手法で事前に計算してテーブル引き、なんて手法があります。

C++14constexprのお陰で、その計算をコンパイル時にやりたい、という要求が生まれたわけですが、どういうわけかこれまでstd::arrayの変更操作(operator[]とか)にはconstexprが指定されていなかったので、Sproutを使って

C++14
SPROUT_CXX14_CONSTEXPR sprout::array<std::uint8_t, 1000> make_mod_table_ysr() {
    sprout::array<std::uint8_t, 1000> re{};
    for (size_t i = 0; i < 1000; ++i) {
        size_t mod = i % 11;
        re[i] = static_cast<std::uint8_t>(mod <= 1 ? 0 : 11 - mod);
    }
    return re;
}

(コードはC++でマイナンバーのチェックデジットを計算する - Qiitaより抜粋)

と書いてきたわけですが、C++17からはようやくarrayクラスを自分で作らなくてもそれができるようになりました。

この他、イテレータ全般も対応したようです。

値を範囲内に収めるclamp()関数を追加

値を範囲内に丸める処理というのは頻出する割にはこれまでC++標準にそれをする関数はなく、std::min/std::maxを使うか三項演算子で書く必要がありました。

C++17になってようやくこれを行う関数が提案され、無事にC++17に採用されました。

引数の順序で賛否両論あるようですが、値・最小値・最大値(・比較関数)の順になりました。

所有権を持たない文字列クラスであるbasic_string_viewを追加

C++には文字列クラスとしてstd::stringがあるわけですが、基本的にはアロケーションが発生し、また部分文字列を取る場合には文字列のコピーが発生します。また場合によっては例外も発生します。

高速化のために、このアロケーションやらコピーを避けようと思うと、std::stringメンバー関数の恩恵が受けられず、極めて冗長なコードを書くことになります。

また関数の引数などで文字列を受け取る時、実行速度を考えるとこれまでconst char*版とstd::stringの2つのオーバーロードを用意する必要がありましたが、これがstd::string_viewで代替できるようになります。

std::string_viewは文字列開始位置のポインタと長さを保持します。これによって文字列のコピーをすることなく文字列を参照できるわけですね。

ただし当たり前ですがNULL終端しないので、文字列へのポインタのみしか受け取らないC APIには直接渡せません。長さも一緒に渡せるように変更するなり、ライブラリ作者に要望しましょう。
ex.) DxLibの作者さんに要望を投げた例: [task]文字列の長さ指定付き関数を追加

slide-1
Boost.勉強会 #21 札幌「C++1zにstring_viewが導入されてうれしいので紹介します」 from @h_hiro_

basic_string::data()メンバ関数の非const版を追加

これはC-likeなAPIと文字列をやり取りしていた人には朗報です。

そろそろWindowsでUTF-16とShift-JISの変換方法をC++erらしくまとめようか
のコードを例に解説します。

Win32API
#include <string>
#include <windows.h>
#include <cstring>
std::wstring shift_jis_to_utf_16(const std::string& str)
{
    static_assert(sizeof(wchar_t) == 2, "this function is windows only");
    const int len = ::MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, nullptr, 0);
    std::wstring re(len * 2 + 2, L'\0');
    if (!::MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, &re[0], len)) {
        //中略
    }
}

C++11以前は、std::basic_stringが所有している文字列を格納しているメモリ領域が連続している保証がありませんでした。

C++11になってこの連続が保証され、std::basic_stringをバッファとして利用する上記のようなコードが書けるようになりました(変数re)。

これの何が嬉しいかというと、自前でメモリー確保してバッファを作ってそれをまたコピーするという作業が不要になってコピー回数とアロケーション回数が減らせる→高速化できるということです。

ところでstd::basic_stringには太古の昔からdata()というメンバ関数がありました。にもかかわらず文字列を格納しているメモリ領域の先頭へのポインタを取るのに&re[0]のように書くのってなんか変です。

理由としてはこれまでメンバ関数data()

const charT* data() const;          // C++03 まで

const charT* data() const noexcept; // C++11 から

のように定義されていて、返却されるポインタがconst修飾されているという問題がありました。

つまり上記の目的のためには&re[0]と書くしかありませんでした(const_castするとUBだった)。

今回非const版が追加されたことで

CharT* data() noexcept; //C++17から

素直に書けるようになりました。

なおC++11時代からそうですが、このポインタをいじって書き込んでいい範囲は[0...size())なので、\0が書き込まれる場所はいじってはいけません。つまりデカ目に確保してあとでresizeしましょう。

この件に関しては @yohhoy さんの

が詳しいので詳細はそちらに譲ります。

ロケール依存なし、フォーマット解析なしの高速な文字列・数値変換関数として、to_chars()とfrom_chars()を追加

これは、この記事を見ているような人に直接恩恵があるわけじゃないですが、間接的に影響があります。

これまでC/C++に標準で提供されていた文字列→数値変換/数値→文字列変換はAPIとしてはたくさんの関数がありましたが、集約されるところstrtol系関数/sprintf関数に集約され、それ以外はそのラッパーとしての存在でした。

これらはまずロケールに依存しており、数値→文字列変換についてはsprintfなのでフォーマット解析が挟まったりと余計な処理が挟まっていました。

今回の追加によってこれとは別系統の変換手段ができたので、例えばJSONのシリアライズ/デシリアライズが速くなるとかライブラリのバグが潰れるとかそういう恩恵があるかなと思います。

hypot()関数の3引数版を追加

皆さん義務教育で3平方の定理とかピタゴラスの定理という名前で習ったアレをやるのがstd::hypotの処理内容です。直角三角形の斜辺の長さの2乗は他の2辺の長さの2乗和に等しい、というあれです。

何に使うかというとゲームなんかで直交座標系において2点間の距離の計算やベクトルの長さを計算するために使うわけですね。

でもこれまで2次元版しかなかったので3次元版もほしい。そういう要望に答えたのがこの変更です。

最大公約数と最小公倍数の関数として、gcd()lcm()を追加

最大公約数と最小公倍数なんてものは義務教育で習う初歩的なものでたいしてコード量もいらない。

// N3845のリファレンス実装例
// 仮にclib名前空間内に置く
namespace clib {

// 任意の整数型で動くテンプレート版のabs
template< class T >
constexpr auto abs( T i ) -> enable_if_t< is_integral<T>{}(), T >
{
    return i < T(0) ? -i : i; }
}

// 二つの実引数が整数型であるかどうかを確認し、また共通の型を返すメタ関数common_int_t
template< class M, class N = M >
using common_int_t = enable_if_t< is_integral<M>{}() and is_integral<N>{}()
, common_type_t<M,N>
> ;

// 最大公約数
template< class M, class N >
constexpr auto gcd( M m, N n ) -> common_int_t<M,N>
{
using CT = common_int_t<M,N>;
return n == 0 ? clib::abs<CT>(m) : gcd<CT,CT>(n, m % n);
}

// 最小公倍数
template< class M, class N >
constexpr auto lcm( M m, N n ) -> common_int_t<M,N>
{ return abs((m / gcd(m,n)) * n); }

しかし、だからこそてんでんばらに書くのではなく、標準化されるべきです。というわけで追加されました。

なお競技プログラミングなどでusing namespace std;してきた人で自前でgcd()/lcm()書いていた人は名前衝突で死にます

std::iteratorクラスを非推奨化

cpprefjpの

このクラスを使用しても、イテレータ定義は簡単にならなかった

という言葉にすべてが集約されるよね。どうせみんなイテレータ作るのに一応継承してたけど自前でイテレータの要件満たすための型定義書いてたでしょ。いらんかったよね。

昨年の初心者C++ AdCの記事である
イテレータの解説をするなんて今更佳代 - Qiita
でも結局自分で型定義し直しています。

そんなことよりはようイテレータがもっとかんたんに書ける仕組みを、syntax sugarをよこせ!

<codecvt>を非推奨化。

みなさん、聞きましたか?この現代社会、文字コード変換すら標準APIを提供できないクソ言語がここにあるようです!

せっかくC++11で問題は山積みながら入ったと思ったらこれだよ!これだからアルファベット至上主義者共は・・・(なぞのとばっちり

というわけでiconvのような外部ライブラリ使うとかUnicode変換くらいなら自前で書くかしましょう。

・・・何を言ってるのかわからねーと思うが、俺もわからねぇ!ニコニコ(く)よかよっぽどクソでしょ。まじでありえねー。

C++の未来

C++はC++標準化委員会(WG21)でその仕様が議論され、規格化され、最終的にISO/IECで標準化され、我々が使うコンパイラなんかはこれに従って実装されます。

そのC++標準化委員会がC++に追加する大きな機能のロードマップは

Current Status : Standard C++で見ることができます。

執筆時点で確認したら

loadmap

となっていますね。

2D graphicsというのが見えると思いますが、これは将来GUIを標準化しようという茨の道へと進むことを暗示しているのでしょうか。

Networkingというのはboost.asio相当のものになると思われます。はやく標準入りしてほしいです。

C++20

C++11でリジェクトされ、内容を縮小して再提案されたC++17でも入らずにいたConceptはC++20に入りそうです。やっとかよ!

追記

C++11からC++17という部分に対する指摘が来ました。意図としては(C++11 ... C++17]という半開区間だったんですががが。

License

CC BY 4.0

CC-BY icon.svg

238
241
2

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
238
241