auto + const + smart pointer = bad mix?
auto + const + (smart) pointer は安全そうでそうではないコードを生むよ、という記事。
auto const thing = new Hoge();
auto
によって推論されるのが T*
だった場合、auto const
は T* const
なので「const
でないオブジェクトを指す const
なポインタ値」という宣言になる。
これを回避するにはauto const*
と宣言する。
そして、smart_pointer の時は const*
と書けない。なぜなら生ポインタではないからだ。完。
解は以下のいずれかになる:
auto thing = std::make_unique<Hoge>();
std::unique_ptr<const Hoge> = std::make_unique<Hoge>();
auto + const + smart pointer の組み合わせは使わない方がよい。
https://cpplover.blogspot.com/2019/07/c20-deprecate-implicit-lambda-capture.html
this がキャプチャされているため、メンバ変数自体はキャプチャではなく this 経由の参照アクセスになる。 そのため、以下のコードは合法である。
class Hoge {
int x;
void Do();
};
void Hoge::Do() {
auto change = [=](auto value){ x = value; };
change(10); // x <- 10
}
[=] はコピーキャプチャであるので安全、という前提によって書かれた以下のようなコードは未定義動作を引き起こす。
class Hoge {
int x;
auto GetClosure() {
return [=](){ return x; };
}
};
int main() {
std::function<int()> f;
{
Hoge h;
f = h.GetClosure(); // [=](){ return this->x; } が f に束縛される
// しかし、ここで h の寿命が切れる
}
int y = f(); // 未定義動作
}
C++17 では this の明示的なキャプチャ [this](){}
と this が指すオブジェクトのコピーキャプチャ [*this](){}
が追加され、部分的に問題は解消された。C++20 では暗黙的な this キャプチャが廃止され、問題は完全に解決される。
[&] キャプチャの挙動は変更されない。
C++ ModulesのHeader units - Qiita
import 構文でモジュール名の代わりにヘッダーファイル名を書くと、そのファイル内の宣言とマクロが使用可能になる。⇒ 従来の include と同様に使える。
ただし、プリプロセッサではなくコンパイル時に処理される。
import some_module;
import "some-header.h";
import <some-header.h>;
export module foo;
export import "some-header.h";
ただし、マクロはエクスポートされない。よって、マクロ群をまとめてインポートするような便利モジュールは書けない?
https://cpplover.blogspot.com/2019/01/2018-11c.html
Concept auto x
はやく欲しいnamespace A::B::C {
std::iota(10) | reverse | filter(rule) | take(5)
C++20 で入るかもしれない std::iota_view は「n 始まりの view で終点は規定されていない。単に for(auto n : std::iota(1))
と使うと無限に実行される。 take(10)
とかの view を組み合わせることで範囲を指定できる。
C++11 から存在している std::iota は、対象とするコンテナの begin と end を受け取って製数列を書き込むものだったので、追加予定の iota とは用途が異なることに注意。
C++ Modules Might Be Dead-on-Arrival
将来の C++ に入る予定のモジュールについて、モジュール依存順のコンパイルが必要になる点を上手く解決しないと、ヘッダーファイルを include する現状の構造よりもビルド時間が遅くなるよ、という記事。あと、モジュールが記述されているファイルをどうやって決定するのかもうまく考える必要がある。(このへん: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0778r0.pdf か)
うまく解決されるとよいけど、C++20 に入るのは難しいんでは……
https://qiita.com/_Nnwww/items/5a242466e94b2e432376
#include <iostream>
using namespace std;
template<typename T>
concept bool Multipliable = // コンセプト
requires (T a, T b) {
a * b;
}; // requires式
auto square(Multipliable i) { // コンセプトを用いた関数宣言
return i * i;
}
struct X{};
int main()
{
int a = 3;
struct X b;
cout << square(a) << endl; // OK
cout << square(b) << endl; // NG
return 0;
}
というように、requiresな式が有効である型のみを受け入れる関数をTemplate Meta Progammingなし(SFINIEなし)で書くことができるのが concept
(C++20から入る?)
conceptとrequiresは別々: concept: 右辺の式の真偽によってコンパイルを通すかどうかを判定する。
#include <iostream>
using namespace std;
template<typename T> concept bool SmallerDouble
= sizeof(T) < sizeof(double);
int main()
{
auto psize = [](SmallerDouble a){
cout << sizeof(a) << endl;
}; // ジェネリックラムダ!
int vint = 1;
double vdouble = 1.0;
psize(vint); // OK
psize(vdouble); // Err
return 0;
}
ここでのSmallerDoubleはdoubleよりサイズが小さい全ての型を指すconceptである。
std::unique_ptr<int> a = std::make_unique<int>(1);
auto f = [b = std::move(a)]() {};
// b は f のライフタイムが切れる時、同時に破棄される。
ここから Rust になるわけですね。
auto f = [](auto... args){};
知らんかった。可変長テンプレート関数書き放題じゃん。
ラムダ式を通じて C++ の特徴的な機能を理解する - Qiita
関数ポインタ ⇒ 関数オブジェクト ⇒ テンプレート + 関数オブジェクト ⇒ ラムダ式という試行錯誤の歴史の流れが分かりやすく書いてあってよかった。
ところで C++20 でラムダ式にテンプレートが使えるようになるそうですね。
C++のenumの値を文字列にできるライブラリnameofがすごい - Qiita
実装: __FUNCSIG__
を駆使する。cast をハードコードして文字列化する。underlying_t が 0 始まりの int でない場合は使えない?頑張っている?
同じ名前空間で定義されている Enum に operator を導入するテクニック。
#include <iostream>
template<int N>
constexpr size_t ConstExprStrlen(const char (&)[N])
{
return N;
}
std::string_view GetStringView()
{
constexpr size_t len = ConstExprStrlen("hogehoge");
return std::string_view("hogehoge", len);
}
int main()
{
std::string_view sv = GetStringView();
std::cout << sv << std::endl;
}
こんな感じに書けば文字列リテラルの型 (const char (&)[N]
) から文字列長をコンパイル時計算して string_view をコンストラクトできる。
実際には、"hogehoge" と書くのが二箇所になってしまうのが嫌なので、
#include <iostream>
#define STRING_VIEW_FOR_STRING_LITERAL(str) std::string_view(str, ConstExprStrlen(str))
template<int N>
constexpr size_t ConstExprStrlen(const char (&)[N])
{
return N;
}
constexpr std::string_view GetStringView()
{
return STRING_VIEW_FOR_STRING_LITERAL("hogehoge");
}
int main()
{
constexpr std::string_view sv = GetStringView();
std::cout << sv << std::endl;
}
こんな感じに書くことになるかなあ。string_view まで constexpr で受けられるようになるのはいいなあ。
ちなみに、string_view リテラルが使える環境なら、そちらを使えばよい。
#include <iostream>
#include <string_view>
using namespace std::literals::string_view_literals;
int main()
{
constexpr std::string_view sv = "hoge"sv;
std::cout << sv << std::endl;
}
ref:
また「なんでデフォルトで strlen を省くような template コンストラクタがないの?」という疑問には下記ページに回答っぽいものがある。
たとえば、下記のようなテンプレートコンストラクタが存在した時、
template<std::size_t n>
constexpr basic_string_view(const CharT(&s)[n]) : basic_string_view(s, n) {}
以下のコードは意図しない string_view が作成されてしまう。
char x[255];
sprintf(x, "hello folks");
// oops, sv.size() == 255!
std::string_view sv(x);
簡単に言うと、string_view のサイズが常に固定長文字列配列のサイズになってしまう。sv.size() が先頭からヌル終端までの長さを返さなくなってしまう。
また、多くのコンパイラは文字列リテラルへの strlen() 呼び出しをコンパイル時計算し定数に置き換えるので std::string_view("hoge")
と書いてあれば効率的になるはずだよ、とのコメントがついている。
仮定: "f:5" という文字列が入力されたら float 型の 5.0f を返す、"i:1" という文字列が入力されたら int 型の 1 を返す constexpr 関数を書きたい
https://hackernoon.com/shared-static-variable-for-all-template-class-instances-eaed385f332b
結論: 基底クラスに静的メンバを持たせてそれを継承したテンプレートクラスを作ろう
当たり前すぎてあんまり面白くないな。
Type Erasure という文字を仕事で書いてるコードの中で読む機会があったので、テンプレートテクニック8章を読み直していた。技法の肝については習得。仮想関数テーブルを自作する方は、クラステンプレート内の static 関数がテンプレート引数によって異なるシンボルとして別のアドレス上に生成されることを利用する。テンプレート派生クラスと非テンプレート基底クラスを利用する方法は、基底クラスの仮想関数テーブルを利用する。ということで、とりあえず何らかの方法で仮想関数的なものを利用するのがスタンダードな技法っぽい。
何に使うのかよく理解してなかったけど「境界チェックされた連続要素(≒配列)へのアクセス」に使える。
つまり、配列を渡すときにポインタ渡しせずに span<T>
で渡すことでコンテナじゃなくても begin()
と end()
とかが取れるようになる。
ということは、配列を span で受けとって range-based for で処理とかもできるのかな。サイズ気にしないで良くなるのは安全で良いですね。
C++20 からは動的なエクステントに対しても使えるようになるみたい。
配列と関数ポインタに関して、関数テンプレートと同様に推論された型を取得する。