4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

voidを返す関数も対等に扱いたい。

Last updated at Posted at 2014-10-17
sample01.cpp
# include <string>
# include <iostream>

std::string get_functype(int) {
    return "You have returned an int value.";
}

std::string get_functype(double) {
    return "You have returned a double value.";
}

int int_func(void) {
    auto ret = 100;
    std::cout << "I will return " << ret << "." << std::endl;
    return ret;
}

double double_func(void) {
    auto ret = 3.14;
    std::cout << "I will return " << ret << "." << std::endl;
    return ret;
}

int main(void) {
    std::cout << get_functype(int_func()) << std::endl;
    std::cout << get_functype(double_func()) << std::endl;
    return 0;
}

これは普通にコンパイルして実行できる。

ありふれた関数のオーバーロード。

I will return 100.
You have returned an int value.
I will return 3.14.
You have returned a double value.

ではこれは?

sample02.cpp
# include <string>
# include <iostream>

std::string get_functype(int) {
    return "You have returned an int value.";
}

std::string get_functype(double) {
    return "You have returned a double value.";
}

std::string get_functype(void) {
    return "You have returned nothing.";
}

int int_func(void) {
    auto ret = 100;
    std::cout << "I will return " << ret << "." << std::endl;
    return ret;
}

double double_func(void) {
    auto ret = 3.14;
    std::cout << "I will return " << ret << "." << std::endl;
    return ret;
}

void void_func(void) {
    std::cout << "I will return nothing." << std::endl;
}

int main(void) {
    std::cout << get_functype(int_func()) << std::endl;
    std::cout << get_functype(double_func()) << std::endl;
    std::cout << get_functype(void_func()) << std::endl;
    return 0;
}

コンパイルできない。

g++ -std=c++1y sample02.cpp -o sample02
sample02.cpp: 関数 ‘int main()’ 内:
sample02.cpp:35:42: エラー: void 式の無効な使用法です
     std::cout << get_functype(void_func()) << std::endl;
                                          ^

voidを返す関数は、普通の関数みたいに戻り値の型で推論できない。

でも、これを対等に扱いたい。この(あるいは、これに近いコードを)コンパイルして実行できるようにしたいの。

で、

sample03.cpp
# include <string>
# include <iostream>

std::string get_functype(int) {
    return "You have returned an int value.";
}

std::string get_functype(double) {
    return "You have returned a double value.";
}

std::string get_functype(void) {
    return "You have returned nothing.";
}

int int_func(void) {
    auto ret = 100;
    std::cout << "I will return " << ret << "." << std::endl;
    return ret;
}

double double_func(void) {
    auto ret = 3.14;
    std::cout << "I will return " << ret << "." << std::endl;
    return ret;
}

void void_func(void) {
    std::cout << "I will return nothing." << std::endl;
}


template<typename RET>
inline auto get_functype_switch_helper(RET(*func)(void)) {
    return get_functype(func());
}

template<>
inline auto get_functype_switch_helper<void>(void(*func)(void)) {
    func();
    return get_functype();
}

template<typename FUNC>
inline auto get_functype_switch(FUNC func) {
    return get_functype_switch_helper<decltype(func())>(func);
}


int main(void) {
    std::cout << get_functype_switch(int_func) << std::endl;
    std::cout << get_functype_switch(double_func) << std::endl;
    std::cout << get_functype_switch(void_func) << std::endl;
    return 0;
}

2つのテンプレート関数を間に挟んで、めでたく解決。

I will return 100.
You have returned an int value.
I will return 3.14.
You have returned a double value.
I will return nothing.
You have returned nothing.

ちょっと醜いけどww

インライン関数にしとけば、おそらく実行時のオーバーヘッドは極小か完全にゼロ。

ついでに、引数も自由に付けられるようにしたいよね。

sample04.cpp
# include <string>
# include <iostream>

std::string get_functype(int) {
    return "You have returned an int value.";
}

std::string get_functype(double) {
    return "You have returned a double value.";
}

std::string get_functype(void) {
    return "You have returned nothing.";
}

int int_func(int n1, int n2) {
    auto ret = n1 + n2;
    std::cout << "I will return " << ret << "." << std::endl;
    return ret;
}

double double_func(double ret) {
    std::cout << "I will return " << ret << "." << std::endl;
    return ret;
}

void void_func(void) {
    std::cout << "I will return nothing." << std::endl;
}


template<typename RET>
struct get_functype_switch_helper{
    template<typename... ARGS>
    static auto x(RET(*func)(ARGS...), ARGS... args) {
        return get_functype(func(args...));
    }
};

template<>
struct get_functype_switch_helper<void>{
    template<typename... ARGS>
    static auto x(void(*func)(ARGS...), ARGS... args) {
        func(args...);
        return get_functype();
    }
};

template<typename FUNC, typename... ARGS>
inline auto get_functype_switch(FUNC func, ARGS... args) {
    return get_functype_switch_helper<decltype(func(args...))>::template x<ARGS...>(func, args...);
}


int main(void) {
    std::cout << get_functype_switch(int_func, 30, 70) << std::endl;
    std::cout << get_functype_switch(double_func, 3.14) << std::endl;
    std::cout << get_functype_switch(void_func) << std::endl;
    return 0;
}

これでOK。

可変長テンプレートと構造体を使った関数テンプレートの部分特殊化ハックで、さらにカオスな感じになってきたw

でも、こういうケースが出現するたびに、いちいちこんなの書くの面倒だし、、

こんなときは、

伝統の、

マクロww

void_switch.h
# ifndef INCLUDED_VOID_SWITCH_H
# define INCLUDED_VOID_SWITCH_H

# define DECLARE_VOID_SWITCH(func1)                                               \
template<typename RET>                                                           \
struct func1##_switch_helper{                                                    \
    template<typename... ARGS>                                                   \
    static auto x(RET(*func2)(ARGS...), ARGS... args) {                          \
        return func1(func2(args...));                                            \
    }                                                                            \
};                                                                               \
                                                                                 \
template<>                                                                       \
struct func1##_switch_helper<void>{                                              \
    template<typename... ARGS>                                                   \
    static auto x(void(*func2)(ARGS...), ARGS... args) {                         \
        func2(args...);                                                          \
        return func1();                                                          \
    }                                                                            \
};                                                                               \
                                                                                 \
template<typename FUNC, typename... ARGS>                                        \
inline auto func1##_switch(FUNC func2, ARGS... args) {                           \
    return func1##_switch_helper<decltype(func2(args...))>                       \
        ::template x<ARGS...>(func2, args...);                                   \
}                                                                                \


# endif
sample05.cpp
# include <string>
# include <iostream>
# include "void_switch.h"

std::string get_functype(int) {
    return "You have returned an int value.";
}

std::string get_functype(double) {
    return "You have returned a double value.";
}

std::string get_functype(void) {
    return "You have returned nothing.";
}

int int_func(int n1, int n2) {
    auto ret = n1 + n2;
    std::cout << "I will return " << ret << "." << std::endl;
    return ret;
}

double double_func(double ret) {
    std::cout << "I will return " << ret << "." << std::endl;
    return ret;
}

void void_func(void) {
    std::cout << "I will return nothing." << std::endl;
}

DECLARE_VOID_SWITCH(get_functype)

int main(void) {
    std::cout << get_functype_switch(int_func, 30, 70) << std::endl;
    std::cout << get_functype_switch(double_func, 3.14) << std::endl;
    std::cout << get_functype_switch(void_func) << std::endl;
    return 0;
}

醜いマクロ宣言はヘッダファイルに追い出して、コードもスッキリ。再利用もできる。

めでたしめでたし。

※途中のテンプレート関数呼び出しで、テンプレートパラメータを引き継ぐように若干修正。

4
5
4

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
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?