c++templatesjava-native-interfacetemplate-classes

How can I prevent template type expansion in C++?


I wrote the following classes which uses member pointer functions:

#include <stdlib.h>
#include <vector>

template<class Type>
class PrimitiveAccessor {
    public :
        PrimitiveAccessor(
            JNIEnv* env, const char name[], const char ctorSig[],
            Type (JNIEnv::*callTypeMethodFunction) (jobject, jmethodID)
        ) {
            this->env = env;
            this->type = (jclass)env->NewGlobalRef(env->FindClass(name));
            this->callTypeMethodFunction = callTypeMethodFunction;
        }
        ~PrimitiveAccessor(){
            env->DeleteGlobalRef(this->type);
        }

    private:
        JNIEnv* env;
        jclass type;
        jmethodID constructorId;
        jmethodID callTypeMethodId;
        Type (JNIEnv::*callTypeMethodFunction) (jobject, jmethodID);
};

class Environment {
    public:
        Environment(JNIEnv* env) {
            this->env = env;
            this->init();
        }
        ~Environment(){
            this->env = 0;
            delete(this->jintAccessor);
            this->jintAccessor = 0;
        }
    private:
        JNIEnv* env;
        PrimitiveAccessor<jint>* jintAccessor;

        void init() {
            jintAccessor = new PrimitiveAccessor<jint>(
                env, "java/lang/Integer",
                "(I)V", &JNIEnv::CallIntMethod
            );
        }
};

But on compiling I obtain the following compilation error:

F:\Shared\Workspaces\Projects\DriverFunctionSupplierNative.cpp: In member function 'void Environment::init()':
F:\Shared\Workspaces\Projects\JNIDriverFunctionSupplierNative.cpp:75:4: error: no matching function for call to 'PrimitiveAccessor<long int>::PrimitiveAccessor(JNIEnv*&, const char [18], const char [5], jint (JNIEnv_::*)(jobject, jmethodID, ...))'
    );
    ^
F:\Shared\Workspaces\Projects\JNIDriverFunctionSupplierNative.cpp:36:3: note: candidate: 'PrimitiveAccessor<Type>::PrimitiveAccessor(JNIEnv*, const char*, const char*, Type (JNIEnv_::*)(jobject, jmethodID)) [with Type = long int; JNIEnv = JNIEnv_; jobject = _jobject*; jmethodID = _jmethodID*]'
   PrimitiveAccessor(
   ^~~~~~~~~~~~~~~~~
F:\Shared\Workspaces\Projects\JNIDriverFunctionSupplierNative.cpp:36:3: note:   no known conversion for argument 4 from 'jint (JNIEnv_::*)(jobject, jmethodID, ...)' {aka 'long int (JNIEnv_::*)(_jobject*, _jmethodID*, ...)'} to 'long int (JNIEnv_::*)(jobject, jmethodID)' {aka 'long int (JNIEnv_::*)(_jobject*, _jmethodID*)'}
F:\Shared\Workspaces\Projects\JNIDriverFunctionSupplierNative.cpp:34:7: note: candidate: 'constexpr PrimitiveAccessor<long int>::PrimitiveAccessor(const PrimitiveAccessor<long int>&)'
 class PrimitiveAccessor {
       ^~~~~~~~~~~~~~~~~

I temporarily fixed it by casting the member function pointer:

void init() {
    jintAccessor = new PrimitiveAccessor<jint>(
        env, "java/lang/Integer",
        "(I)V", (long (JNIEnv::*) (jobject, jmethodID))&JNIEnv::CallIntMethod
    );
}

I noticed that the type passed to the template is expanded: is there a way to avoid this cast?


Solution

  • You need to specify a matching type, not a type that is similar.

    I have also cleaned up lots of your unidiomatic C++. Don't use new; it isn't required. You don't need a user-defined destructor if your data members clean themselves up. You should initialise your data members in the member initialiser list, not in the body of the constructor. Use std::string for strings.

    #include <stdlib.h>
    #include <vector>
    #include <memory>
    #include <string>
    
    struct GlobalRefDeleter {
        JNIEnv* env;
        void operator()(jobject type) {
            env->DeleteGlobalRef(type);
        }
    };
    
    using JClass = std::unique_ptr<_jclass, GlobalRefDeleter>;
    
    JClass getClass(JNIEnv* env, const std::string & name) {
        return { static_cast<jclass>(env->NewGlobalRef(env->FindClass(name.c_str()))), env };
    }
    
    template<class Type>
    class PrimitiveAccessor {
        using CallMethod = Type (JNIEnv::*) (jobject, jmethodID, ...);
        public :
            PrimitiveAccessor(
                JNIEnv* env, const std::string & name, const std::string & ctorSig,
                CallMethod callMethod)
            ) : env(env), type(getClass(env, name)), callMethod(callMethod)
            {
            }
    
        private:
            JNIEnv* env;
            JClass type;
            jmethodID constructorId;
            jmethodID callTypeMethodId;
            CallMethod callMethod;
    };
    
    
    class Environment {
        public:
            Environment(JNIEnv* env) : env(env), jintAccessor(env, "java/lang/Integer", "(I)V", &JNIEnv::CallIntMethod)
            {
            }
        private:
            JNIEnv* env;
            PrimitiveAccessor<jint> jintAccessor;
    };