0

I have the following preprocessor class, for which I would like to have separate implementations for Real or complex data.

PreProcessor.cuh

#ifndef __PREPROCESSOR_TEST_CUH__
#define __PREPROCESSOR_TEST_CUH__

#include <cuda/std/complex>
#include <iostream>

#define DEFINITION_IN_HEADER 1 // change between definition in src or header file

typedef cuda::std::complex<float> ccfloat;
typedef cuda::std::complex<double> ccdouble;

template <template <class...> class TT, class... Args>
std::true_type is_tt_impl(TT<Args...>);
template <template <class...> class TT>
std::false_type is_tt_impl(...);

// use as is_tt<cuda::std::complex, Q>::value
template <template <class...> class TT, class T>
using is_tt = decltype(is_tt_impl<TT>(std::declval<typename std::decay<T>::type>()));

template <typename Tin, typename Tout>
class PreProcessor
{
  public:
    // specialization for complex input
    template <typename Q = Tin>
    typename std::enable_if_t<is_tt<cuda::std::complex, Q>::value, void> run();

    // specialization for real input
    template <typename Q = Tin>
    typename std::enable_if_t<!is_tt<cuda::std::complex, Q>::value, void> run();
};

#if DEFINITION_IN_HEADER
// specialization for complex input
template <typename Tin, typename Tout>
template <typename Q>
typename std::enable_if_t<is_tt<cuda::std::complex, Q>::value, void> PreProcessor<Tin, Tout>::run()
{
    std::cout << "COMPLEX template." << std::endl;
}

// specialization for real input
template <typename Tin, typename Tout>
template <typename Q>
typename std::enable_if_t<!is_tt<cuda::std::complex, Q>::value, void> PreProcessor<Tin, Tout>::run()
{
    std::cout << "REAL template." << std::endl;
}
#endif

#endif //!__PREPROCESSOR_TEST_CUH__

If I define the respective implementations in the header file, all is good. But when I define them in a source file (i.e. DEFINITION_IN_HEADER =0):

PreProcessor.cu

#include "PreProcessor.cuh"

#if !DEFINITION_IN_HEADER

// specialization for complex input
template <typename Tin, typename Tout>
template <typename Q>
typename std::enable_if_t<is_tt<cuda::std::complex, Q>::value, void> PreProcessor<Tin, Tout>::run()
{
    std::cout << "COMPLEX template." << std::endl;
}

// specialization for real input
template <typename Tin, typename Tout>
template <typename Q>
typename std::enable_if_t<!is_tt<cuda::std::complex, Q>::value, void> PreProcessor<Tin, Tout>::run()
{
    std::cout << "REAL template." << std::endl;
}

// instantiation
template class PreProcessor<ccfloat, ccfloat>;
template class PreProcessor<int, int>;

#endif

I get the following error:

[build] main.obj : error LNK2019: unresolved external symbol "public: void __cdecl PreProcessor<class cuda::std::__4::complex<float>,class cuda::std::__4::complex<float> >::run<class cuda::std::__4::complex<float> >(void)" (??$run@V?$complex@M@__4@std@cuda@@@?$PreProcessor@V?$complex@M@__4@std@cuda@@V1234@@@QEAAXXZ) referenced in function "public: void __cdecl Processor<class cuda::std::__4::complex<float>,class cuda::std::__4::complex<float> >::run(void)" (?run@?$Processor@V?$complex@M@__4@std@cuda@@V1234@@@QEAAXXZ)

Or, a bit more readable when I use Processor<int, int> Preal;. What is going on, can I not move SFINAE template speciliazations to src files?

[build] main.obj : error LNK2019: unresolved external symbol "public: void __cdecl PreProcessor<int,int>::run<int>(void)" (??$run@H@?$PreProcessor@HH@@QEAAXXZ) referenced in function "public: void __cdecl Processor<int,int>::run(void)" (?run@?$Processor@HH@@QEAAXXZ)

My main:

main.cu


#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <cuda.h>
#include <cuda/std/complex>

#include "PreProcessor_test.cuh"

template <typename Tin, typename Tout>
class Processor
{
  public:
    PreProcessor<Tin, Tout> *Pp;
    Processor() { Pp = new PreProcessor<Tin, Tout>; }
    ~Processor() { delete Pp; }
    void run() { Pp->run(); }
};

int main()
{
    Processor<int, int> Preal;
    Preal.run();

    using ccfloat = cuda::std::complex<float>;
    Processor<ccfloat, ccfloat> Pcomplex;
    Pcomplex.run();
    return 0;
}
rinkert
  • 6,593
  • 2
  • 12
  • 31
  • This doesn't address the question, but names that contain two consecutive underscores (`__PREPROCESSOR_TEST_CUH__`) and names that begin with an underscore followed by a capital letter are reserved for use by the implementation. Don't use them in your code. – Pete Becker Jul 20 '22 at 15:48

1 Answers1

1

You need also to explicitly instantiate template method:

template void PreProcessor<ccfloat, ccfloat>::run<ccfloat>();
template void PreProcessor<int, int>::run<int>();

Demo

Simpler might be to avoid SFINAE in header and uses it only in implementation part as detail of implementation:

template <typename Tin, typename Tout>
class PreProcessor
{
public:
    void run();
};

in cpp:

namespace {
// specialization for complex input
template <typename Tin, typename Tout>
typename std::enable_if_t<is_tt<cuda::std::complex, Tin>::value, void>
do_run(PreProcessor<Tin, Tout>&)
{
    std::cout << "COMPLEX template." << std::endl;
}

// specialization for real input
template <typename Tin, typename Tout>
typename std::enable_if_t<!is_tt<cuda::std::complex, Tin>::value, void>
do_run(PreProcessor<Tin, Tout>&)
{
    std::cout << "REAL template." << std::endl;
}
}


// specialization for real input
template <typename Tin, typename Tout>
void PreProcessor<Tin, Tout>::run()
{
    do_run(*this);
}

// instantiation
template class PreProcessor<ccfloat, ccfloat>;
template class PreProcessor<int, int>;
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • aha ofc, C++ keeps amazing me. Thanks. Say I write another run method lets say `PrePrecessor::runner`, that will call SFINAEd `run` method, I keep the call within the same translational unit and I dont have to do this explicit instantiation? – rinkert Jul 20 '22 at 16:01
  • To call a template method, it should have been **explicitly** instantiated, else implicit instantiation takes place, and definition should be reachable. – Jarod42 Jul 20 '22 at 16:07