templateで生まれうる再帰型に対する特別な制御の方法について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
zxc
記事: 79
登録日時: 13年前
住所: 日本の背骨(?)あたり

templateで生まれうる再帰型に対する特別な制御の方法について

#1

投稿記事 by zxc » 10年前

コード:

//TemplateClass.hpp
#include<iostream>

template<typename T>
class Wrap{
	private:
		T t;
	public:
		typedef T type;
		explicit Wrap(const T& t_):t(t_){
			std::cout<<"Wrap::Wrap(const T& t_)\n";
		}
		~Wrap(){
			std::cout<<"Wrap::~Wrap()\n";
		}
		T Get()const{
			return t;
		}
};

コード:

//main.cpp
#include<iostream>
#include"TemplateClass.hpp"

using namespace std;


int main(){
	
	cout<<"wi";
	Wrap<int> wi(27);
	cout<<wi.Get()<<"\n";
	
	cout<<"wwi";
	Wrap< Wrap< int> > wwi(Wrap<int>(45));//今回は再帰型と呼びます
	cout<<wwi.Get().Get()<<"\n";
	

	return 0;
}

コード:

//結果
wiWrap::Wrap(const T& t_)
27
wwiWrap::Wrap(const T& t_)
Wrap::Wrap(const T& t_)
Wrap::~Wrap()
45
Wrap::~Wrap()
Wrap::~Wrap()
Wrap::~Wrap()
Wrap::~Wrap()
続行するには何かキーを押してください . . .
  今回はなんと呼ぶのかわからない Wrap<Wrap<T>> かそれ以上内部に同テンプレートクラスでネストを作るような型を再帰型と呼ばせてもらいます。環境はWin7 32bit  MSVC2010です。

 上のようなテンプレートクラスWrap<T>を考えるときに、
  1. TがWrap<U>型であるというパターンを識別する方法は?
  2. TがWrap<U>であるWrap<T>に対して特別な処理を行う
  3. 受け取ったTがWrap<U> 型である場合とそうでない場合でWrap<T>クラスの振る舞いをわける方法
が知りたいです。


Faith and Brave - C++で遊ぼう 20080207 Template Template Parametersのような方法を使うのかもしれないと考え、例えば下のような単純なメタ関数is_wrapを作ってみましたが、機能が十分ではありません。

コード:

////in TemplateClass.hpp
template<template<class> class T>
struct is_wrap:public std::false_type{};

template<>
struct is_wrap<Wrap>:public  std::true_type{};

////////////in main.cpp
	cout<<is_wrap< Wrap >::value;
//	cout<<is_wrap< Wrap<int> >::value;			// error
//	cout<<is_wrap< Wrap<Wrap<int> > >::value;	// error

YuO
記事: 947
登録日時: 14年前
住所: 東京都世田谷区

Re: templateで生まれうる再帰型に対する特別な制御の方法について

#2

投稿記事 by YuO » 10年前

部分特殊化がしたいということでしょうか。

コード:

template <typename T> class Wrap { /* ... */ };
template <typename T> class Wrap<Wrap<T> > { /* ... */ };

zxc
記事: 79
登録日時: 13年前
住所: 日本の背骨(?)あたり

Re: templateで生まれうる再帰型に対する特別な制御の方法について

#3

投稿記事 by zxc » 10年前

 部分特殊化である程度やりたいことは出来そうですが、再帰がより深い型に対して一々定義するのは避けたいのです。私の定義の仕方が悪く、よりよい方法があるのかもしれません。
再帰を持たないWrap<T>型と Wrap<Wrap< ・・・・ T ・・・・ >> 型のような再帰する全ての型(深さは問わない)とを別に扱いたいのです。

コード:

///// in TemplateClass.hpp に追加
template<typename T>
class Wrap<Wrap<T>>{
	private:
		T t;
	public:
		typedef T type;
		explicit Wrap(const T& t_):t(t_){
			std::cout<<"Wrap<Wrap>::Wrap(const T& t_)\n";
		}
		Wrap(const Wrap<T>& w):t(w.Get()){
			std::cout<<"Wrap<Wrap>::Wrap(const Wrap<T>& wt)\n";
		}
		~Wrap(){
			std::cout<<"Wrap<Wrap>::~Wrap()\n";
		}
		T Get()const{
			return t;
		}
};

///in main()
	cout<<"wi";
	Wrap<int> wi(27);
	cout<<wi.Get()<<"\n";
	

	cout<<"wwi";
	Wrap< Wrap< int> > wwi(45);
	cout<<wwi.Get()<<"\n";

	cout<<"wwwd";
	Wrap<Wrap<Wrap<double>>> wwwl( Wrap<double>(2.718) );//冗長


sleep

Re: templateで生まれうる再帰型に対する特別な制御の方法について

#4

投稿記事 by sleep » 10年前

zxc さんが書きました:
  1. TがWrap<U>型であるというパターンを識別する方法は?
  2. TがWrap<U>であるWrap<T>に対して特別な処理を行う
  3. 受け取ったTがWrap<U> 型である場合とそうでない場合でWrap<T>クラスの振る舞いをわける方法
が知りたいです。
再起処理を含めたあまり良い説明が思いつかなかったので、質問内容とは少し異なりますがサンプルコードを載せておきます。理解へのきっかけとなれば幸いです。

下記のコードは、ファイルから1行読み込み、カンマに区切られた各単語を指定した型へキャストしてtupleに格納するコードです。
tuple.hppの
116行目 token_cast_::set 関数で再帰処理
121行目 token::get 関数で型毎に振る舞いを分けています。
各型に対する特別な処理として stoiなど文字列から各型への変換を使用しています。
即席で書いたものなので、処理が適当なのはご容赦ください。

Main.cpp

コード:

#include <iostream>
#include <fstream>
#include <string>
#include "tuple.hpp"
using namespace std;

int main()
{
	ifstream ifs("test.ini");
	if (ifs)
	{
		string line;
		if (getline(ifs, line))
		{
			//12,40,13,40,C:\Users\Public\Downloads\test.txt
			auto tuple = getTuple<int, int, int, int, string>(line, ',');
			if (isOK(tuple))
			{
				cout << token(tuple, 1) << endl;
				cout << token(tuple, 2) << endl;
				cout << token(tuple, 3) << endl;
				cout << token(tuple, 4) << endl;
				cout << token(tuple, 5) << endl;
			}
		}
		if (getline(ifs, line))
		{
			//		 (※空行:\s\t\t\s) 空行のため isOK(tuple)は falseを返すので表示はない。
			auto tuple = getTuple<int, int, int, int, string>(line, ',');
			if (isOK(tuple))
			{
				cout << token(tuple, 1) << endl;
				cout << token(tuple, 2) << endl;
				cout << token(tuple, 3) << endl;
				cout << token(tuple, 4) << endl;
				cout << token(tuple, 5) << endl;
			}
		}
		if (getline(ifs, line))
		{
			//0.57, こんにちは
			auto tuple = getTuple<double, string>(line, ',');
			if (isOK(tuple))
			{
				cout << token(tuple, 1) << endl;
				cout << token(tuple, 2) << endl;
			}
		}
		if (getline(ifs, line))
		{
			//東京都特許許可局, 2, 0.112
			auto tuple = getTuple<string, int, float>(line, ',');
			if (isOK(tuple))
			{
				cout << token(tuple, 1) << endl;
				cout << token(tuple, 2) << endl;
				cout << token(tuple, 3) << endl;
			}
		}
		if (getline(ifs, line))
		{
			//ABC, DEF, 0.57, 3 (4つの内、前3つを取り出す)
			auto tuple = getTuple<string, string, double>(line, ',');
			if (isOK(tuple))
			{
				cout << token(tuple, 1) << endl;
				cout << token(tuple, 2) << endl;
				cout << token(tuple, 3) << endl;
			}
		}
	}
	ifs.close();

	cout << endl << "press any key to continue..." << endl;
	cin.ignore();
	return 0;
}
tuple.hpp

コード:

#pragma once

#include <stdexcept>
#include <iostream>
#include <sstream>
#include <string>
#include <regex>
#include <tuple>
#include <vector>
#include <cstdint>


#define isOK(pair) pair.first
#define token(pair, i) std::get<i-1>(pair.second)


template<std::size_t index, typename Token_Type>
struct token
{
	static inline Token_Type get(std::vector<std::string>& token_list, std::size_t& token_index)
	{
		throw std::logic_error("not implemented type: " + std::string(typeid(Token_Type).name()));
	}
};

template<std::size_t index>
struct token<index, std::int32_t>
{
	static inline std::int32_t get(std::vector<std::string>& token_list, std::size_t& token_index)
	{
		std::size_t idx;
		auto value = stoi(token_list[index], &idx, 10);
		if (idx != token_list[index].size()) throw std::bad_cast();
		token_index--;
		return value;
	}
};

template<std::size_t index>
struct token<index, std::uint32_t>
{
	static inline std::uint32_t get(std::vector<std::string>& token_list, std::size_t& token_index)
	{
		std::size_t idx;
		auto value = stoul(token_list[index], &idx, 10);
		if (idx != token_list[index].size()) throw std::bad_cast();
		token_index--;
		return value;
	}
};

template<std::size_t index>
struct token<index, std::int64_t>
{
	static inline std::int64_t get(std::vector<std::string>& token_list, std::size_t& token_index)
	{
		std::size_t idx;
		auto value = stoll(token_list[index], &idx, 10);
		if (idx != token_list[index].size()) throw std::bad_cast();
		token_index--;
		return value;
	}
};

template<std::size_t index>
struct token<index, std::uint64_t>
{
	static inline std::uint64_t get(std::vector<std::string>& token_list, std::size_t& token_index)
	{
		std::size_t idx;
		auto value = stoull(token_list[index], &idx, 10);
		if (idx != token_list[index].size()) throw std::bad_cast();
		token_index--;
		return value;
	}
};

template<std::size_t index>
struct token<index, float>
{
	static inline float get(std::vector<std::string>& token_list, std::size_t& token_index)
	{
		std::size_t idx;
		auto value = stof(token_list[index], &idx);
		if (idx != token_list[index].size()) throw std::bad_cast();
		token_index--;
		return value;
	}
};

template<std::size_t index>
struct token<index, double>
{
	static inline double get(std::vector<std::string>& token_list, std::size_t& token_index)
	{
		std::size_t idx;
		auto value = stod(token_list[index], &idx);
		if (idx != token_list[index].size()) throw std::bad_cast();
		token_index--;
		return value;
	}
};

template<std::size_t index>
struct token<index, std::string>
{
	static inline std::string get(std::vector<std::string>& token_list, std::size_t& token_index)
	{
		token_index--;
		return token_list[index];
	}
};


template<std::size_t index, typename Tuple_Type>
struct token_cast_
{
	static inline void set(Tuple_Type &tuple, std::vector<std::string>& token_list, std::size_t& token_index)
	{
		using Token_Type = typename std::tuple_element<index, Tuple_Type>::type;
		std::get<index>(tuple) = token<index, Token_Type>::get(token_list, token_index);
		token_cast_<index - 1, Tuple_Type>::set(tuple, token_list, token_index);
	}
};

template<typename Tuple_Type>
struct token_cast_<0, Tuple_Type>
{
	static inline void set(Tuple_Type &tuple, std::vector<std::string>& token_list, std::size_t& token_index)
	{
		using Token_Type = typename std::tuple_element<0, Tuple_Type>::type;
		std::get<0>(tuple) = token<0, Token_Type>::get(token_list, token_index);
	}
};

template<typename Tuple_Type>
struct token_cast
{
	static inline void set(Tuple_Type &tuple, std::vector<std::string>& token_list, std::size_t& token_index)
	{
		token_cast_<std::tuple_size<Tuple_Type>::value - 1, Tuple_Type>::set(tuple, token_list, token_index);
	}
};

template<typename... Tuple_Type>
static inline std::tuple<Tuple_Type...> getTuple(std::vector<std::string>& token_list, std::size_t& token_index)
{
	using Tuple = std::tuple<Tuple_Type...>;
	Tuple tuple;
	token_index = std::tuple_size<Tuple>::value;
	if (token_index > token_list.size()) throw std::out_of_range("tuple index out of range");
	token_cast<Tuple>::set(tuple, token_list, token_index);
	return move(tuple);
}


static inline std::string& trim(std::string& s)
{
	s.erase(std::begin(s), std::find_if_not(std::begin(s), std::end(s), [](const char& c){ return isspace(static_cast<unsigned char>(c)); }));
	s.erase(std::find_if_not(s.rbegin(), s.rend(), [](const char& c){ return isspace(static_cast<unsigned char>(c)); }).base(), std::end(s));
	return s;
}

static inline std::vector<std::string> split(const std::string& s, const char separator)
{
	std::vector<std::string> v;
	std::istringstream iss(s);
	std::string token;
	while (std::getline(iss, token, separator)) v.emplace_back(trim(token));
	return std::move(v);
}

template<typename... Tuple_Type>
static inline std::pair<bool, std::tuple<Tuple_Type...>> getTuple(std::string line, const char separator)
{
	using Tuple = std::tuple<Tuple_Type...>;
	Tuple tuple;
	bool result = false;
	std::size_t token_index = -1;

	if (line[line.size() - 1] == '\r') line.resize(line.size() - 1);
	if (trim(line).size() > 0)
	{
		try
		{
			auto token_list = split(line, separator);
			tuple = getTuple<Tuple_Type...>(token_list, token_index);
			result = true;
		}
		catch (std::bad_cast& ex)
		{
			std::cerr << "Error: " << ex.what() << std::endl;
			std::cerr << "Type: " << typeid(ex).name() << std::endl;
			std::cerr << "token: " << token_index << std::endl;
		}
		catch (std::invalid_argument& ex)
		{
			std::cerr << "Error: " << ex.what() << std::endl;
			std::cerr << "Type: " << typeid(ex).name() << std::endl;
			std::cerr << "token: " << token_index << std::endl;
		}
		catch (std::out_of_range& ex)
		{
			std::cerr << "Error: " << ex.what() << std::endl;
			std::cerr << "Type: " << typeid(ex).name() << std::endl;
			std::cerr << "token: " << token_index << std::endl;
		}
		catch (std::exception& ex)
		{
			std::cerr << "Error: " << ex.what() << std::endl;
			std::cerr << "Type: " << typeid(ex).name() << std::endl;
		}
	}

	return std::make_pair(result, tuple);
}
test.ini

コード:

12,40,13,40,C:\Users\Public\Downloads\test.txt
 		 
0.57, こんにちは
東京都特許許可局, 2, 0.112
ABC, DEF, 0.57, 3

YuO
記事: 947
登録日時: 14年前
住所: 東京都世田谷区

Re: templateで生まれうる再帰型に対する特別な制御の方法について

#5

投稿記事 by YuO » 10年前

zxc さんが書きました: 部分特殊化である程度やりたいことは出来そうですが、再帰がより深い型に対して一々定義するのは避けたいのです。私の定義の仕方が悪く、よりよい方法があるのかもしれません。
再帰を持たないWrap<T>型と Wrap<Wrap< ・・・・ T ・・・・ >> 型のような再帰する全ての型(深さは問わない)とを別に扱いたいのです。
Wrap<T>とWrap<Wrap<T>>が区別できれば,あとはWrap<Wrap<T>>はWrap<T>を使いながら実装できないのでしょうか。

例示されている部分だけだと,以下のようにすれば,
  • wrapping_typeはWrapの直接のテンプレートパラメータ型
  • typeは一番内側のWrapのテンプレートパラメータ型
になりますから,やりたいことを満たせそうに思います。

コード:

#include <iostream>
#include <string>

template <typename T> class Wrap
{
public:
	typedef T wrapping_type;
	typedef T type;
private:
	wrapping_type t;
public:
	explicit Wrap(const type & t_) : t(t_) {}
	type Get() const { return t; }
};

template <typename T> class Wrap<Wrap<T>>
{
public:
	typedef Wrap<T> wrapping_type;
	typedef typename wrapping_type::type type;
private:
	wrapping_type t;
public:
	explicit Wrap(const type & t_) : t(t_) {}
	explicit Wrap(const wrapping_type & t_) : t(t_.Get()) {}
	type Get() const { return t.Get(); }
};

// 面倒なので出力をテンプレート関数にした
template <typename T> void output (const char * prefix, const Wrap<T> & val)
{
	std::cout << prefix << "\n\t" << val.Get() << std::endl;
}

int main(void)
{
	output("wi", Wrap<int>(27));
	output("wwi", Wrap<Wrap<int>>(45));
	output("wwwd", Wrap<Wrap<Wrap<double>>>(2.718));
	output("wwwws", Wrap<Wrap<Wrap<Wrap<std::string>>>>("abc")); // せっかくなのでもっと深いものも。

	return 0;
}

zxc
記事: 79
登録日時: 13年前
住所: 日本の背骨(?)あたり

Re: templateで生まれうる再帰型に対する特別な制御の方法について

#6

投稿記事 by zxc » 10年前

sleep さんが書きました:
zxc さんが書きました:
再起処理を含めたあまり良い説明が思いつかなかったので、質問内容とは少し異なりますがサンプルコードを載せておきます。理解へのきっかけとなれば幸いです。

下記のコードは、ファイルから1行読み込み、カンマに区切られた各単語を指定した型へキャストしてtupleに格納するコードです。
tuple.hppの
116行目 token_cast_::set 関数で再帰処理
121行目 token::get 関数で型毎に振る舞いを分けています。
各型に対する特別な処理として stoiなど文字列から各型への変換を使用しています。
実引数テンプレートを使う場合、利用するテンプレート引数が増えることで複雑になると考えられることと、MSVC2010では可変長テンプレートがまだ使えないので、castする方法で解決しようとする場合はこのようなパターンを取りうるというように捉えようと思います。

YuO さんが書きました: Wrap<T>とWrap<Wrap<T>>が区別できれば,あとはWrap<Wrap<T>>はWrap<T>を使いながら実装できないのでしょうか。

例示されている部分だけだと,以下のようにすれば,
  • wrapping_typeはWrapの直接のテンプレートパラメータ型
  • typeは一番内側のWrapのテンプレートパラメータ型
になりますから,やりたいことを満たせそうに思います。

コード:

#include <iostream>
#include <string>

template <typename T> class Wrap
{
public:
	typedef T wrapping_type;
	typedef T type;
private:
	wrapping_type t;
public:
	explicit Wrap(const type & t_) : t(t_) {}
	type Get() const { return t; }
};

template <typename T> class Wrap<Wrap<T>>
{
public:
	typedef Wrap<T> wrapping_type;
	typedef typename wrapping_type::type type;
private:
	wrapping_type t;
public:
	explicit Wrap(const type & t_) : t(t_) {}
	explicit Wrap(const wrapping_type & t_) : t(t_.Get()) {}
	type Get() const { return t.Get(); }
};

// 面倒なので出力をテンプレート関数にした
template <typename T> void output (const char * prefix, const Wrap<T> & val)
{
	std::cout << prefix << "\n\t" << val.Get() << std::endl;
}

int main(void)
{
	output("wi", Wrap<int>(27));
	output("wwi", Wrap<Wrap<int>>(45));
	output("wwwd", Wrap<Wrap<Wrap<double>>>(2.718));
	output("wwwws", Wrap<Wrap<Wrap<Wrap<std::string>>>>("abc")); // せっかくなのでもっと深いものも。

	return 0;
}
 ご指摘のとおり、Wrap<T>への委譲と、Wrap<Wrap<T>>におけるTとWrap<T>とをしっかり分けて考えられなかったことが今回のような質問の原因となったかもしれません。
 上のコードのようなWrap<Wrap<T>>クラスと今までの同名クラスを取り替えたところ、意図通りの動作となったので解決とさせていただきます。お二人ともありがとうございました。

sleep

Re: templateで生まれうる再帰型に対する特別な制御の方法について

#7

投稿記事 by sleep » 10年前

質問が手段に見えていましたが
私が考えていたほど複雑な目的ではなかった様ですね。

STLのコンテナの場合、内部にネストした型の値を返すだけで、外部の型(vector<vector<T>>型)の振る舞いをその内部にネストした型(vector<T>型)が何の型(T)をインプリメントしてインスタンス化されているのかを外部から判断しません。
ネストされた内部の型の振る舞いは 内部の型が決定し、外部の型はそれについて関与しません。
外部の型(Wrap<Wrap<U>>型)から ネストした内部の型(<Wrap<U>型)が何の型(U)をインプリメントしているのかを外部の型から判断し、外部の型の振る舞いを決定する必要がある設計の場合、
実現されるのは、外部の1つの振る舞いを実行した際に 内部にインプリメントされている型を判断し振る舞いを分岐していく処理です。

この場合、
Wrap<Wrap<U>>型の振る舞いが Uの型が何であったかに関わらず1種類しか存在しないのであればそれほど問題とはなりませんが
Uに指定される型毎に(Wrap<U>型ではなく)それをインプリメントする外部の型(Wrap<Wrap<U>>型)の振る舞いを変更しなければならない場合
どうしても外側のWrap型(Wrap<Wrap<U>>型)から まずはTがWrap<U>型であること、次にインプリメントされているWrap<U>型のUが何であるかを判断する必要があります。
SFINAEで解決をはかる場合、通常は実引数で解決するか、外部の型で制御するのではなく内部の型に処理をまかせます。

型のネスト数およびネストした型毎によって分岐する再帰呼び出しが手段でしかないのであれば、目的によっては他の解決方法を模索した方が良く 原点に立ち戻り 目的を見極める必要があります。

zxc
記事: 79
登録日時: 13年前
住所: 日本の背骨(?)あたり

Re: templateで生まれうる再帰型に対する特別な制御の方法について

#8

投稿記事 by zxc » 10年前

 ご指摘に対する答えになるかはわかりませんが、あくまで今回の質問は文法的な問題だと自分は思っています。また、これは私が質問前まで実装に使うことのできないパターンのひとつであり、目的を達成するためのより良い実装にするのに必要になるかもしれないパターンの一つだと考えたため質問しました。手段に関する質問だと私は思います。


 templateなクラスを作るとして、それを利用するときに想定されている意図に反するようなtemplateクラスの使用(今回は再帰)があれば、
  • 何らかの方法で弾く
  • 意図通りの動作に変換する
といういずれかもしくは両方をするつもりです。前提に再帰的かどうかをどうにか識別しなければならないはずです。
 例えばstd::vector等コンテナやstd::pairの再帰型は好みはあるかもしれませんが、利用方法としてはありえるものだと思います。しかし、一部のtemplateクラスではそのような振る舞いが例外的になりうると思います。また、templateクラスの多くは実際に利用する場合はtypedefをすることでそれが実際に何型であるかは隠すと考えられるため、「気をつけて」コーディングする方法は使うべきではないと考え、このような手段がどのように可能かを質問しました。

sleep

Re: templateで生まれうる再帰型に対する特別な制御の方法について

#9

投稿記事 by sleep » 10年前

今回いただいた回答を読んで、Wrap<Wrap<U>>のような実装は望んでいない(意図に反している)ということが分かりました。
そして、Wrap<Wrap<U>>もしくはそれ以上ネストされた場合もWrap<U>と同じ動作をさせる様にすることで解決を考えていたことも分かりました。

目的は、templateに意図と反した型が指定された場合の対応 ということですね。
つまり、Wrap型のtemplate引数として 意図されている型と意図されていない型 が存在する、ということです。
意図されていない型が指定されると問題があるので
zxc さんが書きました:  templateなクラスを作るとして、それを利用するときに想定されている意図に反するようなtemplateクラスの使用(今回は再帰)があれば、
  • 何らかの方法で弾く
  • 意図通りの動作に変換する
といういずれかもしくは両方をするつもりです。
という対応を考えていたということですね。

Wrap型が許容する意図した型は何種類ありますか? また、許容しない意図と反した型は何種類ありますか?
意図の意味する定義が明確であることはとても重要です。曖昧なままの想定であればいくら実装を考えてもすぐに前提は崩れます。

例えば、意図している型のみ許容できれば良いのであれば以下のような方法があります。
制限をかけることになるので堅牢です。
下記のコードでは、templateの引数が数値型とstring型だった場合のみtemplateをインスタンス化させます。
許容したい型を追加したい場合は、enable_ifの条件式に許容したい型を追記すれば良いです。

コード:

#include <iostream>
#include <string>
#include <type_traits>
using namespace std;

template<
	typename T,
	class = typename enable_if< is_arithmetic<T>::value || is_same<T, string>::value >::type
>
class Wrap
{
public:
	Wrap()
	{
		cout << "T == " << typeid(T).name() << ": " << (is_arithmetic<T>::value || is_same<T, string>::value) << endl;
	}
private:
	T value;
};

int main()
{
	Wrap<int> a;
	Wrap<double> b;
	Wrap<string> c;
	//Wrap<Wrap<int>> wi;  //コンパイルエラーになる(class = typename enable_if<...> を外すとコンパイルできる)
	//Wrap<Wrap<Wrap<int>>> wwi;  //コンパイルエラーになる(class = typename enable_if<...> を外すとコンパイルできる)
	cin.ignore();
}

zxc
記事: 79
登録日時: 13年前
住所: 日本の背骨(?)あたり

Re: templateで生まれうる再帰型に対する特別な制御の方法について

#10

投稿記事 by zxc » 10年前

  1. 現在別のコードが不要な再帰型でバグや予想していないような処理をしている可能性があるのでその識別・判断のため
  2. ある種のハンドルやポインタ、参照のような機能のクラスを作ると仮定した場合、再帰的な型は考えてみた限りでは不要(何かのハンドル(=ポインタ/参照)のハンドルよりは何かのハンドルが良い)
  3. typedef等で別名を与えられたWrap<U>クラスをWrap<T>のテンプレート引数に受け取ってしまうことを上記2つの理由等でを避けるために人力で判別するのは不確実->判断できるコードが書けないといけない
簡単にまとめると上の3つの理由から再帰型か非再帰型かどうかが重要で、他の型はあとで考えればいいと思っています。

sleep

Re: templateで生まれうる再帰型に対する特別な制御の方法について

#11

投稿記事 by sleep » 10年前

typedef等で置き換えると別の何かになると思い込んでいませんか?

コード:

#include <iostream>
#include <string>
#include <type_traits>
using namespace std;

template<typename T>
class Wrap {
public:
	Wrap() { cout << "T == " << typeid(T).name() << " : " << typeid(T).raw_name() << endl; }
private:
	T value;
};

typedef Wrap<int> type1;
typedef Wrap<type1> type2;
typedef type2 type3;

template<typename T>
void func(Wrap<T> a)
{
	cout << "T == " << typeid(T).name() << " : " << typeid(T).raw_name() << endl;
}

int main()
{
	Wrap<type3> a;             // A
	cout << endl;

	Wrap<Wrap<Wrap<int>>> wi;  // B
	cout << endl;

	typedef Wrap<Wrap<type1>> type4;
	type4 c;                   // C
	func(c);
	cout << endl;

	typedef Wrap<int> type5;
	type5 d;                   // D
	func(d);

	cin.ignore();
}
それと typedefは実体のある型に対してしか適用できないので、templateをインスタンス化できなければ typedefはコンパイルエラーで失敗しますよ。
3の状況に陥るのは 意図に反しているにも関わらず、templateのインスタンス化を成功させてしまっているからではないでしょうか。

コード:

#include <iostream>
#include <string>
#include <type_traits>
using namespace std;

template<
	typename T,
	class = typename enable_if< is_arithmetic<T>::value || is_same<T, string>::value >::type
>
class Wrap
{
public:
	Wrap()
	{
		cout << "T == " << typeid(T).name() << ": " << (is_arithmetic<T>::value || is_same<T, string>::value) << endl;
	}
private:
	T value;
};

typedef Wrap<Wrap<int>> type;  //インスタンス化の条件が満たせず、コンパイルが失敗する(ちなみに static_assertだと成功する 違いに注意)

int main()
{
}
3についてはここまでに説明したとおりです。私も人ではなくコンパイラに判断させた方が良いと思います。
1、2については憶測のみで見定めず、検証して確証を得た方が良いと思います。
むしろ、きちんと調べていないのに何かはっきりしていないものを解決しようとされていたことにびっくりしました。
第三者の視点として指摘できそうなところはこんなところですかね。

私は説明いただいた範囲の情報しか持ち合わせていないので、潜在的な問題については見ることができません。
なので、曖昧な確証のない情報が絡むと的確な返答を返せない恐れがあります。
また、判明している範囲内での指摘はさせていただきますが、実際にどうするかは zxcさん次第なので 今の判断のままでも私は良いと思ってますよ。
私の指摘はあくまで掲示板上で得た情報からの第三者の視点に過ぎませんから 例えそれが適格だろうが不適格だろうが従う必要はないのです。
それに確実に私より問題を解決するための情報をお持ちですしね。

zxc
記事: 79
登録日時: 13年前
住所: 日本の背骨(?)あたり

Re: templateで生まれうる再帰型に対する特別な制御の方法について

#12

投稿記事 by zxc » 10年前

 直前の、 1.2.について、どう動いているのかどう制御できるのか確かめたいので、そういうことをする文法はどのようなものですかと聞いたつもりで、また、3については下のようなことを想定しています。これらが再帰型の制御によって可能だろうかと考えるためです。
 そしてこれら1.2.3.は再帰型がどうして今回の質問で特殊化されるような特別な型なのかの明示であり、質問した事柄そのものではありません。これらは今回の質問で解決される事柄ではなく、今回の質問で解決された事柄で解決されうる問題だとと思います。

コード:


typedef Wrap<int> SomeHandle;
・・・・・

Wrap<SomeHandle>; //このような型は要らない
 
 どうしてかは知りませんが誤解がある気がします。「今回の質問の解決する=今回の質問の直前に挙げた1.2.3.が解決する」では無いはずです。
 今回の質問の最終的な目的はあくまで文法だとかパターンの問題の解決であって、それを参考に別のコードを改善しようと考えているのです。前者はここで解決してますが、後者については解決したかどうか言ってませんし解決していません。1.2.3.はどうして再帰型に注目しているかの明示です。


 あくまで今回の質問はtemplateなクラスで生まれうる再帰型の識別・特殊化等別の処理の方法についてです。例えばそれは(裏にvectorのようなクラスを作るだとかの理由で)良くわからないoperator[]のオーバーロードについて尋ねたようなもので、そもそもがそれがどんなものかもわからないのにその手段を比較して選ぶことは出来ないでしょう。比較するためにはある程度理解しなければならない(というのが今回の質問の暗黙の前提で)、しかし自力ではわからないから質問したのです。

閉鎖

“C言語何でも質問掲示板” へ戻る