[C++][Qt]windeployqtをCMakeから実行する。

動機

オープンソース版のQtは基本的にLGPLライセンスであるため、ライセンスの汚染を避けるためには動的リンクが必要になる。この場合、何らかのアプリケーションを作って配布するときは、Qtの関連する共有ライブラリなどをアプリケーションに同梱して配布することが望ましい。

QtはWindowwsとMacに対しては、それぞれwindeployqt、macdeployqtという、必要な共有ライブラリを自動的にコピーしてくれるツールが用意されている。Linuxに関しては残念ながらQtオフィシャルなものはないが、有志の方々がlinuxdeployqtというツールを開発し公開している。
このうちwindeployqtをCMake上で実行してみたい。

本記事はWindowsのみを対象としているが、MacLinuxの場合も似たようなものだろうとは思う。

方法

cmake_minimum_required(VERSION 3.14)

#...諸々のプロジェクトの設定

get_target_property(_qmake_path Qt5::qmake IMPORTED_LOCATION)
get_filename_component(_qt_bin_path ${_qmake_path} DIRECTORY)

find_program(DEPLOYQT NAMES windeployqt HINTS "${_qt_bin_path}")

add_custom_command(
    TARGET ${PROJECT_NAME} POST_BUILD
    COMMAND ${DEPLOYQT} $<TARGET_FILE_DIR:${PROJECT_NAME}>/$<TARGET_FILE_NAME:${PROJECT_NAME}>
        --$<IF:$<CONFIG:Debug>,debug,release> --dir "$<TARGET_FILE_DIR:${PROJECT_NAME}>")
install(CODE "execute_process(COMMAND ${DEPLOYQT} ${CMAKE_INSTALL_PREFIX}/$<TARGET_FILE_NAME:${PROJECT_NAME}>
                              --$<IF:$<CONFIG:Debug>,debug,release> --qmldir ${CMAKE_SOURCE_DIR})")

次のような動作をさせている。

  1. qmakeを探す。
  2. qmakeのディレクトリからwindeployqtを探す。
  3. ビルド後とインストール後にそれぞれ出力された実行ファイルに対してwindeployqtを実行させる。

install(CODE ...)の中でのGenerator Expressionsの使用にはCMake3.14以上が必要であるため、cmake_minimum_required(VERSION 3.14)としている。

閑話

windeployqtを使うと、動作に必須でない大量のdllや実行ファイルまでもがコピーされてしまうのに、肝心のOpenSSLなどはコピーされないという残念極まりない仕様なので、はっきり言って使いにくい。
vcpkgでインストールしたQtを使うと、いちいちwindeployqtなんて面倒なものを使わずとも必要最小限のdllをきっちりコピーしてくれて1助かるのだが、あちらはあちらで膨大なバグを抱え込んでいるので正直まともに使えない。此度、Qt5.15.2とQt WebEngineをvcpkgからインストールしようとしたら、"ファイルパス長すぎ"と文句を言われビルドもできなかった。vcpkgをアップデートしたらQtのビルドでコケた。仕方ないので、vcpkgを無効にしてQtやその他必要なライブラリを独立にインストールした。ホント勘弁してほしい……。


  1. ただしビルド時のみである。インストール時のコピーは自前で行う必要がある。

[C++]ふと文字列リテラルによる名前付きtupleが欲しくなった話。

何らかのクラスをタグとして与えるtuple、なら作る方法はネット上にいくらでも転がっているが、タグ用のクラスをいちいち宣言しなければならないのが鬱陶しいし、名前空間を汚してしまうのも気になるので、文字列リテラルで与えられるようにできないかなぁとちょっと思った結果がこれである。C++20では非型テンプレートパラメータに(色々と制限はあるものの)クラス型を指定できるようになったので、実現可能になった。
いやあんまり用途はないんだけど、非常に限定的な状況下ではあってもいいような気がして。特に最近はtupleが入れ子になったような、要素が非常に多くなってしまう面倒なものを作っていることもあり、いくつものインデックスで与えるのがちょっと野暮ったく感じていたのだ。

#include <tuple>
#include <iostream>
#include <limits>
#include <cassert>

namespace nt
{

template <size_t N>
struct char_array
{
    constexpr char_array(const char(&s)[N])
    {
        for (size_t i = 0; i < N; ++i) name[i] = s[i];
    }
    template <size_t M>
    constexpr bool operator==(const char_array<M>& that) const
    {
        if constexpr (N != M) return false;
        else
        {
            for (size_t i = 0; i < N; ++i) if (name[i] != that.name[i]) return false;
            return true;
        }
    }
    static constexpr size_t size() { return N; }
    char name[N];
};

template <char_array N, class T> struct name_type { static constexpr char_array name = N; using type = T; };

template <class ...T>
class named_tuple : public std::tuple<typename T::type...>
{
    static constexpr std::tuple<char_array<T::name.size()>...> name = { T::name... };
public:
    template <size_t N, size_t I = 0>
    static constexpr size_t find_name(char_array<N> n)
    {
        if constexpr (I < sizeof...(T))
        {
            if (std::get<I>(name) == n) return I;
            return find_name<N, I + 1>(n);
        }
        else return std::numeric_limits<size_t>::max();
    }
    using std::tuple<typename T::type...>::tuple;
};

template <char_array Name, class ...T>
decltype(auto) get(named_tuple<T...>& t) { return std::get<named_tuple<T...>::find_name(Name)>(t); }
template <char_array Name, class ...T>
decltype(auto) get(const named_tuple<T...>& t) { return std::get<named_tuple<T...>::find_name(Name)>(t); }
template <char_array Name, class ...T>
decltype(auto) get(named_tuple<T...>&& t) { return std::get<named_tuple<T...>::find_name(Name)>(std::move(t)); }

}

int main()
{
    nt::named_tuple<nt::name_type<"i32", int>, nt::name_type<"f32", float>, nt::name_type<"string", std::string>> nt(1, 2.3f, "4.5");
    std::cout << get<"i32">(nt)<< std::endl;//1
    assert(get<"i32">(nt) == std::get<0>(nt));
    std::cout << get<"f32">(nt)<< std::endl;//2.3
    assert(get<"f32">(nt) == std::get<1>(nt));
    std::cout << get<"string">(nt) << std::endl;//4.5
    assert(get<"string">(nt) == std::get<2>(nt));
}

[C++]アロー演算子オーバーロードでtuple、pairその他一時オブジェクトを返したい場合。

久々の投稿だけど小ネタ。独自クラスのイテレータを設計しているときにちょっと悩んだので。

例えば何らかのイテレータを作っていたとして、そのイテレータの間接演算子が参照型ではなく、何らかの一時オブジェクトを返すものだったとする。

struct iterator
{
    std::pair<int&, std::string&> operator*() const { return { *i, *s }; }
    //他のイテレータっぽい演算子とかは省略する。
    int* i;
    std::string* s;
};
int main()
{
    int i = 1;
    std::string s = "234";
    iterator it{ &i, &s };

    std::cout << i << " " << s << std::endl;

    (*it).first = 5;
    (*it).second = "678";

    std::cout << i << " " << s << std::endl;
}

iterator保有している値への参照をstd::pairへと纏めて返している。std::pair全体では参照でもなんでもない一時オブジェクトだ。

さてこのとき、iteratorにアロー演算子を追加したいと考えた。だがアロー演算子は通常、何らかのオブジェクトへのポインタを返すものだ。ただし今回返したいのはstd::pairの一時オブジェクトであるため、ポインタを返すという行為そのものが不可能である。

struct iterator
{
    //絶対ダメ。
    //std::pair<int&, std::string&>* operator->() const { return &std::pair<int&, std::string&>{ i, s }; }
};

上のコードでは、アロー演算子によって返されたstd::pair一時オブジェクトは即時に寿命が尽きるので、firstsecondへアクセスするときには中身は不定値である。したがって、絶対にしてはいけない。

ではどうするのか? 面倒だが、ポインタを代替するクラスを作ることで回避できる。

struct pointer_proxy
{
    std::pair<int&, std::string&> x;
    std::pair<int&, std::string&>* operator->() { return &x; }
};

struct iterator
{
    std::pair<int&, std::string&> operator*() const { return { *i, *s }; }

    pointer_proxy operator->() const { return pointer_proxy{ { *i, *s } }; }

    int* i;
    std::string* s;
};

int main()
{
    int i = 1;
    std::string s = "2.34";
    iterator it{ &i, &s };

    std::cout << i << " " << s << std::endl;

    it->first = 5;
    it->second = "6.78";

    std::cout << i << " " << s << std::endl;

    return 0;
}

こちらの方法では、一時オブジェクトへのポインタではなくpointer_proxyクラスの一時オブジェクトを返している。面倒なことにpointer_proxyの方にもアロー演算子オーバーロードが必要だが、何とかアロー演算子経由のアクセスが可能になる。

何かすごい不細工なので、もっといい方法があったら教えて……。