Search results for '메타프로그래밍'

Inner Class의 완전(명시적) 특화

2008/09/02 20:36
C++ 템플릿 특화중에 특이한 사실중에 하나는, Inner Class의 완전특화가 C++표준이 아니라는것이다. 그래서 Inner Class를 완전특화에서 사용하면 gcc에서는 컴파일이 안된다.

회사에서 매주 월요일 점심시간 짜투리에 CodeDojo 라는 이름으로 Programming Challenges 책의 문제를 푼다.(하긴, 이것도 월요일날 학교를 가게되어서 앞으로는 하지 못할것 같다.) 문제들은 전형적인 ACM 문제유형으로 인풋이 주어지면 문제를 해결한 적절한 아웃풋을 만들어내야하는 식이다.

참여횟수가 늘어나면 인풋을 적절히 일반화를 해보고자 하여 템플릿을 사용하게 되었는데, 이를테면 인풋의 값을 string 형태로 보관한다거나 혹은 int 형태로 보관할수 있게 만들고자했다. 여기에 보통 '0 0'를 입력하면 입력을 종료한다는 조건이 붙는데 stirng형이면 zero가 char형의 '0'가 될 것이고 int 형이면 zero가 int형의 0이 되도록 하기 위해 템플릿 완전(명시적) 특화를 사용하려 하였다.

그런데 VS에서 멀쩡히 컴파일되던 녀석이 테스트 봇 - gcc를 사용한다 - 에서 컴파일이 안되어서 여기저기 알아보니 클래스 내의 Inner Class는 템플릿의 명시적 특화가 '확장이 아닌 표준을 따르면' 지원되지 않는덴다. 그래서 꼼수로 생각한게 부분특화를 사용하고 템플릿 인자에 기본인자를 사용하였더니 gcc나 VS에서 문제없이 컴파일이 되었다.

아래는 이렇게 작성된 부분특화용 Inner Class.

(Language : cpp)
//////////////////////////////////////////////////////////////////////
//
// Input Processor Interface
//
//////////////////////////////////////////////////////////////////////

class InputProcessor
{
public:

    //  break type
    enum BREAK_CONDITION
    {
        BREAK_EOF = 0,
        BREAK_ENTER,
        BREAK_ZERO,

        BREAK_TOTAL
    };

    // 생성자 / 소멸자
    InputProcessor(BREAK_CONDITION _eBreak) { eBreakCondition_ = _eBreak; }
    virtual ~InputProcessor(){}

    // 인터페이스
    virtual void  process()              = 0;
    virtual int   getDataIndexCount()    = 0;
    virtual const std::vector<int>* getDigits(int _nIndex)   = 0;
    virtual const std::vector<std::string>* getChars(int _nIndex)   = 0;

    // Test를 위한 함수
    virtual void addTestInput(const char* pSzTestData) = 0;

protected:

    // break condition
    BREAK_CONDITION  eBreakCondition_;

    // data manipulator
    // 로컬 템플릿 클래스는 명시적 특화가 허용되지 않고 부분특화만 허용됨. 그에 대한 꽁수
    // cf. 비주얼스튜디오 확장으로 로컬클래스의 명시적 특화가 허용된다.
    template< typename T, typename PartialSpecialization = bool >
    struct SDataManipulator{};
    template< typename PartialSpecialization >
    struct SDataManipulator< int, PartialSpecialization >
    {
        static int convertData(char* _pSzData){ return atoi( _pSzData ); }
        static const int getZero(){return 0;}
        static const std::vector<int>* getDigits(
                std::vector< std::vector<int>* >& _refVecData,
                int _nIndex)
        {
            return _refVecData[ _nIndex ];
        }
        static const std::vector<std::string>*  getChars(
                std::vector< std::vector<int>* >& _refVecData,
                int _nIndex)
        {
            return 0;
        }
    };
    template< typename PartialSpecialization >
    struct SDataManipulator< std::string, PartialSpecialization >
    {
        static char* convertData(char* _pSzData){ return _pSzData; }
        static const char* getZero(){ return "0";}
        static const std::vector<int>* getDigits(
                std::vector< std::vector<std::string>* >& _refVecData,
                int _nIndex)
        {
            return 0;
        }
        static const std::vector<std::string>*  getChars(
                std::vector< std::vector<std::string>* >& _refVecData,
                int _nIndex)
        {
            return _refVecData[ _nIndex ];
        }
    };

    // multiline data
    template< typename T >
    struct SInputData
    {
        // data container
        std::vector< std::vector<T>* > aVecVecData;

        // get data
        void processData(BREAK_CONDITION _eBreak, const char* _pSzManualData )
        {
            char buf[512];
            bool bLoop = true;
            while( bLoop )
            {
                if( _pSzManualData == 0 )
                {
                    // 라인으로 입력받기
                    if( gets( buf ) == 0 )
                        return;
                }
                else
                {
                    strcpy(buf, _pSzManualData);
                    bLoop = false;
                }

                std::vector<T>*  pVector = new std::vector<T>;
                char* pSzEach = strtok(buf, " ");
                while (pSzEach!= 0)
                {
                    pVector->push_back(
                                SDataManipulator<T>::convertData( pSzEach )
                    );
                    pSzEach = strtok(0, " ");
                }

                // Zero Break
                if( _eBreak == BREAK_ZERO && pVector->size() == 2 )
                {
                    if( (*pVector)[0] == SDataManipulator<T>::getZero() &&
                        (*pVector)[1] == SDataManipulator<T>::getZero() )
                    {
                        delete pVector;
                        return;
                    }
                }

                aVecVecData.push_back( pVector );

                // Enter Break
                if( _eBreak == BREAK_ENTER )
                    return;
            }
        }

        // clear data
        void clearData()
        {
            int nSize = (int)aVecVecData.size();
            for( int i = 0; i < nSize; ++i)
            {
                delete aVecVecData[i];
            }
            aVecVecData.clear();
        }
    };

};


//////////////////////////////////////////////////////////////////////
//
// Input Processor Implimentation
//
//////////////////////////////////////////////////////////////////////

template< typename T >
class InputProcessorImpl : public InputProcessor
{
public:
    InputProcessorImpl(BREAK_CONDITION _eBreak) : InputProcessor(_eBreak) {}
    virtual ~InputProcessorImpl(){ aData_.clearData(); }

    virtual void process()
    {
        if( aData_.aVecVecData.empty() == false )
                aData_.clearData();
        aData_.processData( eBreakCondition_, 0);
    }

    virtual const std::vector<int>* getDigits(int _nIndex)
    {
        if( _nIndex < 0 ||
            aData_.aVecVecData.empty() ||
            _nIndex >= getDataIndexCount() )
            return 0;

        return InputProcessor::SDataManipulator<T>::getDigits(aData_.aVecVecData, _nIndex);
    }

    virtual const std::vector<std::string>* getChars(int _nIndex)
    {
        if( _nIndex < 0 ||
             aData_.aVecVecData.empty() ||
              _nIndex >= getDataIndexCount() )
            return 0;

        return InputProcessor::SDataManipulator<T>::getChars(aData_.aVecVecData, _nIndex);
    }

    virtual int getDataIndexCount()
    {
        return (int)aData_.aVecVecData.size();
    }

    virtual void addTestInput(const char* pSzTestData)
    {
        aData_.processData(InputProcessor::BREAK_EOF, pSzTestData );
    }

private:
    InputProcessor::SInputData< T > aData_;
};

아래는 보너스로, 위의 인풋프로세스를 이용하여 푼 LCD문제. (물론 봇테스트도 통과, 최적화는 되어있지 않다)
http://www.programming-challenges.com/pg.php?page=viewsubmission&subid=211071

아래는 위의 일반화된 인풋이전에 인풋을 나누어 사용했을때 풀었던 문제 MineSweeper.
http://www.programming-challenges.com/pg.php?page=viewsubmission&subid=200889

프로그래밍 C++, 메타프로그래밍

메타는 지하로....

2008/07/26 18:51
나에게 있어서, 보고 감동한 소스들 중에는 '타입리스트'가 항상 들어간다.

참여중인 프로젝트에서 타입리스트를 사용하여 'enum'의 사용량을 줄여보려고 시도해본적이 있었다. 그러니까, enum대신에 TL::IndexOf< T_Typlist, MyClass >::value 등의 값을 사용하려고 했었다. 이렇게 하면, 사용하려는 클래스군 - 아마, state 나 strategy pattern 들의 구현클래스들이겠지 - 이 추가될때 마나 T_Typelist 에 추가되는 클래스만 적어주면 된다.

적용을 해봤는데, 다른 분들의 반응이 영 탐탁치 않다. 아마도 익숙한 enum을 나두고 왜 굳이 생소한 타입리스트를 사용하는지에 대한 불만의 표출이었으리라.

곰곰히 자기반성을 해보니, 이건 '잘난체' 외에 아무것도 아니라는걸 느꼈다. 나에게 반문해보니 enum과 class의 중복등록이 정말 나빠서 없애려고 했던것 보다는 타입리스트를 프로젝트에서 써보고 싶었던것이 더 크게 작용한것같다. 이러한 행동은 팀워크를 깨뜨리고 분위기를 싸~ 하게 만드는, 얻는것은 없고 잃는것은 많은 '못된'짓이다.

팀은 새로운것을 즐기는 사람, 코딩보다는 결과물을 더 즐기는 사람, 익숙한것들 활용하여 문제를 해결하려는 사람등 다양한 사람들로 구성된다. 그리고 이러한 팀원들의 합동작업으로 최종 결과물이 나온다. 새로운것을 즐기는 사람이 짠 코드를 익숙한 것을 활용하여 문제를 해결하려는 사람이 유지보수를 맡게 되는 경우도 허다하다. 그래서 다른 팀원이 공감할수 없는 코드은 '못된'코드다.

다른분들에게 생소한 메타프로그래밍은, 정말로 쓰이면 크게 좋을때, 외부 노출을 최대한 적게 하여 만들어야 한다. 그리고, 메타프로그래밍을 사용했을때의 장점을 충분히 납득시켜야 한다.

결론? 메타프로그래밍은 코드 밑단의, 지하세계를 지키는일에 사용하자.

일상 이야기 메타프로그래밍, 팀워크

타입리스트와 재귀적 상속

2008/07/24 22:28
(회사에서 삽질하며 만든코드인데, 쓸일이 없어져서 폐기해버려서 슬쩍 포스팅 해본다.)
그러니까, 이런걸 만들고 싶은거였다.

타입리스트에 있는 항목들을 하나씩 인스턴스로 자동으로 생성하게 하기

어떤 오브젝트가 있고 이것은 다수의 모듈들의 상호작용으로 기능을 수행한다.
그런데 모듈들을 하나하나 enum화 하는것은 귀찮은 일이다.

그래서 모듈들을 타입리스트의 묶고 이 모듈 타입리스트에 있는 항목에 대해 하나씩 인스턴스를 생성하게 하고 싶다. 문제는, 타입리스트의 메타함수 결과값들은 컴파일 시점에 만들어지므로 for문을 돌며 인스턴스를 생성하는것이 불가능하다는것.

(Language : cpp)
// 아래의 코드는 컴파일 되지 않는다.
for(int i = 0; i < TL::Length<T_TypeList>::value; ++i)
{
    m_Modules[ i ] = new TL::TypeAt< T_TypeList, i>
}


이때 예전에 봐두었던 재미난 코드가 생각이 났다.
Template Non-Type Parameter 밑에 있는, 재귀적 상속.

그렇다! N계층으로 재귀적으로 상속이 컴파일 인스턴스 되면,  생성자가 N번 불리므로, 원하는 작업을 할수 있다!

(Language : cpp)
void InitContext()
{
    // ...

    m_pMockModuleGen = new T_MockModuleGen;

    MakeUsingModule<
        T_TypeList, TL::Length<T_TypeList>::value> makeIt(m_pMockModuleGen);

    // ...
}

(Language : cpp)
template<typename T_TypeList, int i>
struct MakeUsingModule : public MakeUsingModule<T_TypeList, i-1>
{
    MakeUsingModule( MockModuleGenerator* pModuleGen)
        : MakeUsingModule<T_TypeList, i-1>(pModuleGen)
    {
        pModuleGen->CreateUsingModule< TL::TypeAt<T_TypeList, i-1>::Result >();
    }
};

template<typename T_TypeList>
struct MakeUsingModule<T_TypeList, 0>
{
    MakeUsingModule(MockModuleGenerator* pModuleGen)
    {
    }
};

타입리스트에 대응하는 for문을 발견한 위대한(!) 순간이었다.

프로그래밍 C++, 메타프로그래밍, 타입리스트